.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
).
В системе, поддерживающей оба метода, одним из возможных преимуществ является то, что код in .init
запускается до, .ctors
а код - .fini
после .dtors
. Если порядок важен, это как минимум один грубый, но простой способ различить функции инициализации / выхода.
Основным недостатком является то, что вы не можете легко иметь более одной _init
и одной _fini
функции на каждый загружаемый модуль и, вероятно, придется фрагментировать код более .so
чем мотивированным. Другое состоит в том, что при использовании метода компоновщика, описанного выше, один заменяет оригинальный _init и _fini
функции по умолчанию (предоставленные crti.o
). Это где все виды инициализации обычно происходят (в Linux это где глобальное назначение переменных инициализируется). Обход, который описан здесь
Обратите внимание на ссылку выше, что каскадирование к оригиналу _init()
не нужно, так как он все еще на месте. Однако call
встроенная сборка является x86-мнемонической, и вызов функции из сборки выглядел бы совершенно иначе для многих других архитектур (например, для ARM). Т.е. код не прозрачный.
.init
/ .fini
и .ctors
/ .detors
механизмы похожи, но не совсем. Код в .init
/ .fini
работает "как есть". Т.е. у вас может быть несколько функций в .init
/ .fini
, но синтаксически сложно AFAIK поместить их там полностью прозрачно в чистом C, не разбивая код на множество маленьких .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))
было бы трудно "вычеркнуть" макрос, если бы был только один набор скобок.