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

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

  1. когда именно он работает?
  2. почему есть две круглые скобки?
  3. Is __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];
}

5 ответов


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

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

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


.init/.fini не исключено. Это все еще часть стандарта эльфов, и я осмелюсь сказать, что это будет навсегда. Код .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/выход.

основным недостатком является то, что вы не можете иметь больше чем один _init и _fini функция на каждый загружаемый модуль и, вероятно, придется фрагментировать код в более .so чем мотивировано. Другое дело, что при использовании метода компоновщика, описанного выше, заменяется исходный _init и _fini функции по умолчанию (обеспечивается crti.o). Здесь обычно происходят все виды инициализации (в Linux инициализируется назначение глобальных переменных). Способ обойти это описано здесь

обратите внимание на ссылку выше, что каскадирование к оригиналу _init() не требуется, поскольку он все еще на месте. The в ассемблерной является 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.т. a. (В проекте на C++, например, было бы полезно, если бы что-то работает до или после глобальных конструкторов).

я бы предпочел __attribute__((constructor))/((destructor)) где возможный, простое и элегантное решение даже оно чувствует как обжуливать. Для таких кодеров, как я, это не всегда вариант.

некоторые хорошие ссылки в книге компоновщики и погрузчики.


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

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

использование 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 класс извергает это сердце из до осажденного/destructor"... толпа ликует) enter image description here


вот еще один конкретный пример.Это для общей библиотеки. Основная функция общей библиотеки-взаимодействие с устройством чтения смарт-карт. Но он также может получать информацию о конфигурации во время выполнения через UDP. Udp обрабатывается потоком, который должны быть запущен во время init.

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

  }

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