it-swarm.com.ru

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

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

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

Пример в Objective-C :

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

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}
314
Casebash
  1. Он запускается при загрузке разделяемой библиотеки, обычно во время запуска программы.
  2. Вот как все атрибуты GCC; по-видимому, чтобы отличить их от вызовов функций.
  3. GCC-специфический синтаксис.
  4. Да, это работает на C и C++.
  5. Нет, функция не должна быть статичной.
  6. Деструктор запускается, когда разделяемая библиотека выгружается, как правило, при выходе из программы.

Таким образом, способ, которым работают конструкторы и деструкторы, состоит в том, что совместно используемый объектный файл содержит специальные разделы (.ctors и .dtors в ELF), которые содержат ссылки на функции, отмеченные соответственно атрибутами конструктора и деструктора. Когда библиотека загружается/выгружается, программа динамического загрузчика (ld.so или somesuch) проверяет, существуют ли такие разделы, и, если это так, вызывает функции, на которые есть ссылки.

Если подумать, в обычном статическом компоновщике, возможно, есть нечто подобное, так что один и тот же код запускается при запуске/завершении работы независимо от того, выбирает ли пользователь статическую или динамическую компоновку.

257
janneb

.init/.fini не считается устаревшим. Это все еще часть стандарта ELF, и я бы осмелился сказать, что так будет всегда. Код в .init/.fini запускается загрузчиком/компоновщиком времени выполнения, когда код загружается/выгружается. То есть при каждой загрузке ELF (например, совместно используемой библиотеки) будет выполняться код в .init. По-прежнему возможно использовать этот механизм для достижения примерно того же, что и с __attribute__((constructor))/((destructor)). Это старая школа, но у нее есть некоторые преимущества.

Например, для механизма .ctors/.dtors требуется поддержка system-rtl/loader/linker-script. Это далеко не обязательно будет доступно во всех системах, например, глубоко встроенных системах, где код выполняется на голом железе. То есть даже если GCC поддерживает __attribute__((constructor))/((destructor)), он не уверен, что он будет работать, поскольку компоновщик может его организовать, а загрузчик (или, в некоторых случаях, загрузочный код) его запустит. Чтобы использовать вместо этого .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-мнемоническим, и вызов функции из Assembly будет выглядеть совершенно по-разному для многих других архитектур (например, 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)), где это возможно, это простое и элегантное решение, даже если оно похоже на читерство. Для голых металлистов, таких как я, это не всегда вариант.

Несколько хороших ссылок в книге Линкеры и загрузчики .

60
Michael Ambrus

Эта страница дает хорошее представление о реализации атрибутов constructor и destructor и разделах внутри ELF, которые позволяют им работать. После усвоения информации, представленной здесь, я собрал немного дополнительной информации и (заимствуя пример раздела у Майкла Амбруса выше) создал пример, чтобы проиллюстрировать концепции и помочь моему обучению. Эти результаты представлены ниже вместе с примером источника.

Как объяснялось в этой теме, атрибуты constructor и destructor создают записи в разделах .ctors и .dtors объектного файла. Вы можете разместить ссылки на функции в любом разделе одним из трех способов. (1) используя любой атрибут section; (2) атрибуты constructor и destructor или (3) с вызовом inline-Assembly (как указано по ссылке в ответе Амбруса).

Использование атрибутов constructor и destructor позволяет дополнительно назначить приоритет конструктору/деструктору для управления порядком его выполнения до вызова main() или после его возврата. Чем ниже заданное значение приоритета, тем выше приоритет выполнения (более низкие приоритеты выполняются до более высоких приоритетов перед main () - и после более высоких приоритетов после main ()). Заданные вами значения приоритета должны быть больше 100, так как компилятор резервирует значения приоритета от 0 до 100 для реализации. Aconstructor или destructor, указанное с приоритетом, выполняется до того, как constructor или destructor указано без приоритета.

С атрибутом section или с inline-Assembly вы также можете разместить ссылки на функции в разделе кода ELF .init и .fini, который будет выполняться перед любым конструктором и после любого деструктора, соответственно. Любые функции, вызываемые ссылкой на функцию, размещенную в разделе .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

Этот пример помог закрепить поведение конструктора/деструктора, надеюсь, он будет полезен и другим.

34
David C. Rankin

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

Xcode использует "глобальное" "пользовательское значение по умолчанию", чтобы решить, какой класс XCTestObserver извергает свое сердце в осажденному Консоль.

В этом примере ... когда я неявно загружаю эту psuedo-библиотеку, давайте назовем ее ... 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 по-прежнему преобладает, , как и здесь, )

enter image description here

С флагом компоновщика -ldemure.a ... (Понятные результаты, gasp ... "спасибо constructor/destructor" ... Толпа ура enter image description here

7
Alex Gray

Вот еще один конкретный пример. Это для общей библиотеки. Основная функция общей библиотеки - связь с устройством чтения смарт-карт. Но он также может получать "информацию о конфигурации" во время выполнения через udp. UDP обрабатывается потоком, который ДОЛЖЕН запускаться во время инициализации.

__attribute__((constructor))  static void startUdpReceiveThread (void) {
    pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
    return;

  }

Библиотека была написана в c.

1
drlolly