Как именно работает __attribute __ ((конструктор))? - PullRequest
316 голосов
/ 13 января 2010

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

  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];
}

Ответы [ 5 ]

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

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

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

60 голосов
/ 03 марта 2013

.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)), где это возможно, это простое и элегантное решение, даже если оно похоже на читерство. Для голых металлических кодеров, таких как я, это не всегда вариант.

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

34 голосов
/ 23 июня 2014

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

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

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

С атрибутом section или с inline-сборкой вы также можете поместить ссылки на функции в секции кода .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

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

7 голосов
/ 21 сентября 2014

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

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 по умолчанию, , как требуется, здесь )

enter image description here

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

1 голос
/ 14 декабря 2016

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

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

  }

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...