.init/ .finiне застаріло. Це все ще є частиною стандарту ELF, і я наважусь сказати, що це буде назавжди. Код в .init/ .finiзапускається завантажувачем / програмою-лінкером, коли код завантажується / вивантажується. Тобто на кожному завантаженні ELF (наприклад, спільна бібліотека) .initбуде запущений код у . Ще можна використовувати цей механізм для досягнення приблизно того ж, що і з __attribute__((constructor))/((destructor)). Це стара школа, але вона має деякі переваги.
.ctors/ .dtorsмеханізм, наприклад, вимагає підтримки system-rtl / loader / linker-script. Це далеко не напевно доступне для всіх систем, наприклад, глибоко вбудованих систем, де код виконується на голому металі. Тобто, навіть якщо __attribute__((constructor))/((destructor))він підтримується GCC, невідомо, що він запуститься, оскільки це залежить від того, що він може його організувати, і завантажувач (або в деяких випадках завантажувальний код) для його запуску. Для використання .init/ .finiнатомість найпростішим способом є використання прапорів-лінкерів: -init & -fini (тобто з командного рядка GCC, це був би синтаксис -Wl -init my_init -fini my_fini).
У системі, що підтримує обидва методи, однією з можливих переваг є те, що код у .initзапускається до, .ctorsа код - .finiпісля .dtors. Якщо замовлення доречне, це хоча б один неочищений, але простий спосіб розрізнити функції init / exit.
Основним недоліком є те, що ви не можете легко мати більше однієї _initта однієї _finiфункції на кожен завантажуваний модуль, і, ймовірно, доведеться фрагментувати код більш .soніж мотивованим. Інша полягає в тому, що, використовуючи описаний вище метод лінкера, один замінює оригінальні функції _init та _finiза замовчуванням (надаються crti.o). Тут зазвичай відбуваються всілякі ініціалізації (в Linux це ініціалізація глобальних змінних). Шлях, описаний тут, описаний
Зауважте за посиланням вище, що каскадування оригіналу _init()не потрібно, оскільки воно все ще існує. Однак callвбудована збірка є x86-мнемонічною, а виклик функції з складання виглядав би зовсім інакше для багатьох інших архітектур (наприклад, ARM). Тобто код не прозорий.
.init/ .finiта .ctors/ .detorsмеханізми схожі, але не зовсім. Код в .init/ .finiпрацює "як є". Тобто ви можете мати кілька функцій в .init/ .fini, але синтаксично важко розмістити їх там, у чистому С, без розбиття коду у багатьох невеликих .soфайлах.
.ctors/ .dtorsорганізовані інакше, ніж .init/ .fini. .ctors/ .dtorsрозділи - це просто таблиці з покажчиками на функції, а "абонент" - це цикл, що надається системою, який викликає кожну функцію опосередковано. Тобто виклик циклу може бути специфічним для архітектури, але оскільки він є частиною системи (якщо вона взагалі існує, тобто), це не має значення.
Наступний фрагмент додає нові функції покажчиків до .ctorsмасиву функцій, головним чином так само, як і __attribute__((constructor))це (метод може співіснувати з __attribute__((constructor))).
#define SECTION( S ) __attribute__ ((section ( S )))
void test(void) {
printf("Hello\n");
}
void (*funcptr)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
Також можна додати покажчики функцій до зовсім іншого самостійно винайденого розділу. У такому випадку потрібен модифікований сценарій зв’язку та додаткова функція, що імітує завантажувач .ctors/ .dtorsцикл. Але за допомогою цього можна досягти кращого контролю над порядком виконання, додавати вхідний аргумент і повертати код обробки етапу (наприклад, у проекті C ++, було б корисно, якщо вам потрібно щось, що працює до або після глобальних конструкторів).
Я вважаю за краще, __attribute__((constructor))/((destructor))де це можливо, це просте і елегантне рішення, навіть таке відчуття, як обман. Для гофрованих металевих кодерів, як я, це просто не завжди варіант.
Кілька хороших посилань у книзі Повізники та навантажувачі .
#define __attribute__(x)). Якщо у вас є кілька атрибутів, наприклад,__attribute__((noreturn, weak))важко буде "макрос", якби був лише один набір дужок.