Как именно работает __attribute__((конструктор))?

Кажется, довольно ясно, что он должен все настроить.

  1. Когда именно он запускается?
  2. Почему есть две скобки?
  3. Является __attribute__ функция? Макрос? Синтаксис?
  4. Это работает в C? C++?
  5. Должна ли функция, с которой она работает, быть статичной?
  6. Когда делает __attribute__((destructor)) бежать?

Пример в Задаче C:

__attribute__((constructor))
static void initialize_navigationBarImages() {
  navigationBarImages = [[NSMutableDictionary alloc] init];
}

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}

5 ответов

Решение
  1. Он запускается при загрузке разделяемой библиотеки, обычно во время запуска программы.
  2. Вот как все атрибуты GCC; по-видимому, чтобы отличить их от вызовов функций.
  3. GCC-специфический синтаксис.
  4. Да, это также работает в C и C++.
  5. Нет, функция не должна быть статичной.
  6. Деструктор запускается, когда разделяемая библиотека выгружается, как правило, при выходе из программы.

Таким образом, способ, которым работают конструкторы и деструкторы, состоит в том, что совместно используемый объектный файл содержит специальные разделы (.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

Я бы хотел..

  1. При нагрузке (т.е. когда XCTest загружает мой тестовый пакет), переопределить "по умолчанию" XCTest класс "наблюдатель"... (через constructor функция) PS: Насколько я могу сказать... все, что сделано здесь, может быть сделано с эквивалентным эффектом в моем классе " + (void) load { ... } метод.

  2. запустить мои тесты.... в этом случае с меньшим количеством пустых подробностей в журналах (реализация по запросу)

  3. Вернуть "глобальный" 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.

Другие вопросы по тегам