Как именно работает __attribute__((конструктор))?
Кажется, довольно ясно, что он должен все настроить.
- Когда именно он запускается?
- Почему есть две скобки?
- Является
__attribute__
функция? Макрос? Синтаксис? - Это работает в C? C++?
- Должна ли функция, с которой она работает, быть статичной?
- Когда делает
__attribute__((destructor))
бежать?
__attribute__((constructor))
static void initialize_navigationBarImages() {
navigationBarImages = [[NSMutableDictionary alloc] init];
}
__attribute__((destructor))
static void destroy_navigationBarImages() {
[navigationBarImages release];
}
5 ответов
- Он запускается при загрузке разделяемой библиотеки, обычно во время запуска программы.
- Вот как все атрибуты GCC; по-видимому, чтобы отличить их от вызовов функций.
- GCC-специфический синтаксис.
- Да, это также работает в C и C++.
- Нет, функция не должна быть статичной.
- Деструктор запускается, когда разделяемая библиотека выгружается, как правило, при выходе из программы.
Таким образом, способ, которым работают конструкторы и деструкторы, состоит в том, что совместно используемый объектный файл содержит специальные разделы (.ctors и.dtors в ELF), которые содержат ссылки на функции, отмеченные соответственно атрибутами конструктора и деструктора. Когда библиотека загружается / выгружается, программа динамического загрузчика (ld.so или somesuch) проверяет, существуют ли такие разделы, и, если это так, вызывает функции, на которые есть ссылки.
Если подумать, в обычном статическом компоновщике, возможно, есть нечто подобное, так что один и тот же код запускается при запуске / завершении работы независимо от того, выбирает ли пользователь статическое или динамическое связывание.
.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
петля нужна в таком случае. Но с его помощью можно лучше контролировать порядок выполнения, добавлять аргументы в аргументах и обрабатывать код возврата (например, в проекте C++ это было бы полезно, если нужно что-то запустить до или после глобальных конструкторов).
я бы предпочел __attribute__((constructor))/((destructor))
где это возможно, это простое и элегантное решение, даже если вы чувствуете себя обманщиком. Для таких программистов, как я, это не всегда вариант.
Несколько хороших ссылок в книге " Линкеры и загрузчики".
Эта страница дает отличное понимание о constructor
а также destructor
реализация атрибутов и разделы внутри ELF, которые позволяют им работать. После усвоения информации, представленной здесь, я собрал немного дополнительной информации и (заимствуя пример раздела у Майкла Амбруса выше) создал пример, чтобы проиллюстрировать концепции и помочь моему обучению. Эти результаты представлены ниже вместе с примером источника.
Как объяснено в этой теме, constructor
а также destructor
атрибуты создают записи в .ctors
а также .dtors
раздел объектного файла. Вы можете разместить ссылки на функции в любом разделе одним из трех способов. (1) используя либо section
атрибутов; (2) constructor
а также destructor
атрибуты или (3) с вызовом inline-сборки (как указано в ссылке в ответе Амбруса).
Использование constructor
а также destructor
Атрибуты позволяют дополнительно назначать приоритет конструктору / деструктору, чтобы контролировать его порядок выполнения перед main()
вызывается или после его возвращения. Чем ниже заданное значение приоритета, тем выше приоритет выполнения (более низкие приоритеты выполняются до более высоких приоритетов перед main() - и после более высоких приоритетов после main()). Заданные вами значения приоритета должны быть больше100
так как компилятор резервирует значения приоритета от 0 до 100 для реализации.constructor
или же destructor
указанный с приоритетом выполняется до constructor
или же destructor
указано без приоритета.
С помощью атрибута section или встроенной сборки вы также можете поместить ссылки на функции в .init
а также .fini
Раздел кода ELF, который будет выполняться перед любым конструктором и после любого деструктора, соответственно. Любые функции, вызываемые ссылкой на функцию, помещенную в .init
раздел, будет выполняться перед самой ссылкой на функцию (как обычно).
Я попытался проиллюстрировать каждый из них в следующем примере:
#include <stdio.h>
#include <stdlib.h>
/* test function utilizing attribute 'section' ".ctors"/".dtors"
to create constuctors/destructors without assigned priority.
(provided by Michael Ambrus in earlier answer)
*/
#define SECTION( S ) __attribute__ ((section ( S )))
void test (void) {
printf("\n\ttest() utilizing -- (.section .ctors/.dtors) w/o priority\n");
}
void (*funcptr1)(void) SECTION(".ctors") =test;
void (*funcptr2)(void) SECTION(".ctors") =test;
void (*funcptr3)(void) SECTION(".dtors") =test;
/* functions constructX, destructX use attributes 'constructor' and
'destructor' to create prioritized entries in the .ctors, .dtors
ELF sections, respectively.
NOTE: priorities 0-100 are reserved
*/
void construct1 () __attribute__ ((constructor (101)));
void construct2 () __attribute__ ((constructor (102)));
void destruct1 () __attribute__ ((destructor (101)));
void destruct2 () __attribute__ ((destructor (102)));
/* init_some_function() - called by elf_init()
*/
int init_some_function () {
printf ("\n init_some_function() called by elf_init()\n");
return 1;
}
/* elf_init uses inline-assembly to place itself in the ELF .init section.
*/
int elf_init (void)
{
__asm__ (".section .init \n call elf_init \n .section .text\n");
if(!init_some_function ())
{
exit (1);
}
printf ("\n elf_init() -- (.section .init)\n");
return 1;
}
/*
function definitions for constructX and destructX
*/
void construct1 () {
printf ("\n construct1() constructor -- (.section .ctors) priority 101\n");
}
void construct2 () {
printf ("\n construct2() constructor -- (.section .ctors) priority 102\n");
}
void destruct1 () {
printf ("\n destruct1() destructor -- (.section .dtors) priority 101\n\n");
}
void destruct2 () {
printf ("\n destruct2() destructor -- (.section .dtors) priority 102\n");
}
/* main makes no function call to any of the functions declared above
*/
int
main (int argc, char *argv[]) {
printf ("\n\t [ main body of program ]\n");
return 0;
}
выход:
init_some_function() called by elf_init()
elf_init() -- (.section .init)
construct1() constructor -- (.section .ctors) priority 101
construct2() constructor -- (.section .ctors) priority 102
test() utilizing -- (.section .ctors/.dtors) w/o priority
test() utilizing -- (.section .ctors/.dtors) w/o priority
[ main body of program ]
test() utilizing -- (.section .ctors/.dtors) w/o priority
destruct2() destructor -- (.section .dtors) priority 102
destruct1() destructor -- (.section .dtors) priority 101
Этот пример помог закрепить поведение конструктора / деструктора, надеюсь, он будет полезен и другим.
Вот "конкретный" (и, возможно, полезный) пример того, как, почему и когда использовать эти удобные, но неприглядные конструкции...
Xcode использует "глобальное" "пользовательское значение по умолчанию", чтобы решить, какие XCTestObserver
класс извергает свое сердце осажденной консоли.
В этом примере... когда я неявно загружаю эту псевдо-библиотеку, давайте назовем ее... libdemure.a
через флаг в моей тестовой цели а-ля..
OTHER_LDFLAGS = -ldemure
Я бы хотел..
При нагрузке (т.е. когда
XCTest
загружает мой тестовый пакет), переопределить "по умолчанию"XCTest
класс "наблюдатель"... (черезconstructor
функция) PS: Насколько я могу сказать... все, что сделано здесь, может быть сделано с эквивалентным эффектом в моем классе "+ (void) load { ... }
метод.запустить мои тесты.... в этом случае с меньшим количеством пустых подробностей в журналах (реализация по запросу)
Вернуть "глобальный"
XCTestObserver
класс к его первозданному состоянию.. чтобы не запутывать другихXCTest
пробеги, которые не попали на подножку (ака. связаны сlibdemure.a
). Я думаю, это исторически было сделано вdealloc
... но я не собираюсь возиться с этой старой ведьмой.
Так...
#define USER_DEFS NSUserDefaults.standardUserDefaults
@interface DemureTestObserver : XCTestObserver @end
@implementation DemureTestObserver
__attribute__((constructor)) static void hijack_observer() {
/*! here I totally hijack the default logging, but you CAN
use multiple observers, just CSV them,
i.e. "@"DemureTestObserverm,XCTestLog"
*/
[USER_DEFS setObject:@"DemureTestObserver"
forKey:@"XCTestObserverClass"];
[USER_DEFS synchronize];
}
__attribute__((destructor)) static void reset_observer() {
// Clean up, and it's as if we had never been here.
[USER_DEFS setObject:@"XCTestLog"
forKey:@"XCTestObserverClass"];
[USER_DEFS synchronize];
}
...
@end
Без флага компоновщика... (Модно-полицейский рой Купертино требует возмездия, но здесь, по желанию, здесь преобладает дефолт Apple)
С -ldemure.a
флаг компоновщика... (понятные результаты, задыхаясь... "спасибо constructor
/destructor
"... толпа ура)
Вот еще один конкретный пример. Это для общей библиотеки. Основная функция общей библиотеки - связь с устройством чтения смарт-карт. Но он также может получать "информацию о конфигурации" во время выполнения через UDP. UDP обрабатывается потоком, который ДОЛЖЕН быть запущен во время инициализации.
__attribute__((constructor)) static void startUdpReceiveThread (void) {
pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
return;
}
Библиотека была написана в c.