.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
и одну _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
. Но с его помощью можно лучше контролировать порядок выполнения, добавлять аргументы и возвращать код обработки e.t.a. (Например, в проекте C ++ было бы полезно, если бы что-то требовалось до или после глобальных конструкторов).
Я бы предпочел __attribute__((constructor))/((destructor))
, где это возможно, это простое и элегантное решение, даже если оно похоже на читерство. Для голых металлических кодеров, таких как я, это не всегда вариант.
Несколько хороших ссылок в книге Компоновщики и загрузчики .