Jak dokładnie działa atrybut ((konstruktor))?

Wydaje się całkiem jasne, że to ma wszystko ustawić.

  1. kiedy dokładnie działa?
  2. Dlaczego są dwa nawiasy?
  3. Czy __attribute__ jest funkcją? Makro? Składnia?
  4. czy to działa w C? C++?
  5. czy funkcja, z którą pracuje, musi być statyczna?
  6. Kiedy __attribute__((destructor)) ucieka?

Przykład w Objective C :

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

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}
Author: Martin Broadhurst, 2010-01-13

5 answers

  1. jest uruchamiany po załadowaniu biblioteki współdzielonej, zazwyczaj podczas uruchamiania programu.
  2. tak wyglądają wszystkie atrybuty GCC; prawdopodobnie, aby odróżnić je od wywołań funkcji.
  3. składnia specyficzna dla GCC.
  4. tak, to działa również w C i C++.
  5. nie, funkcja nie musi być statyczna.
  6. Destruktor jest uruchamiany, gdy biblioteka współdzielona jest rozładowana, zazwyczaj przy wyjściu programu.

Więc sposób pracy konstruktorów i destruktorów polega na tym, że wspólne plik obiektu zawiera sekcje specjalne (.ctors i .dtors na ELF), które zawierają odniesienia do funkcji oznaczonych odpowiednio atrybutami constructor i destructor. Po załadowaniu / rozładowaniu biblioteki program dynamic loader (ld.so lub somesuch) sprawdza, czy takie sekcje istnieją, a jeśli tak, wywołuje funkcje w nich wymienione.

Jeśli się nad tym zastanowić, prawdopodobnie jest jakaś podobna magia w normalnym linkerze statycznym, więc ten sam kod jest uruchamiany podczas uruchamiania / zamykania niezależnie od tego, czy użytkownik wybiera łącza statyczne czy dynamiczne.

 242
Author: janneb,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-12 13:54:07

.init/.fini nie jest przestarzały. To wciąż część standardu elfów i ośmielę się powiedzieć, że będzie na zawsze. Kod w .init/.fini jest uruchamiany przez loader / runtime-linker, gdy kod jest ładowany/rozładowywany. Tzn. przy każdym załadowaniu ELF (na przykład biblioteka współdzielona) zostanie uruchomiony kod .init. Nadal można użyć tego mechanizmu, aby osiągnąć mniej więcej to samo, co w przypadku __attribute__((constructor))/((destructor)). To stara szkoła, ale ma pewne zalety.

.ctors/.dtors mechanizm wymaga np. wsparcia przez system-rtl/loader / linker-script. Nie jest pewne, czy będzie to dostępne we wszystkich systemach, na przykład głęboko osadzonych systemach, w których kod wykonuje się na gołym metalu. Np. nawet jeśli {[6] } jest wspierane przez GCC, nie jest pewne, czy będzie działać, ponieważ to do linkera należy uporządkowanie go i do loadera (lub w niektórych przypadkach, boot-code), aby go uruchomić. Do użycia .init/.fini zamiast tego najprostszym sposobem jest użycie FLAG linkera: - INIT & - fini(tzn. z linii poleceń GCC składnia byłaby -Wl -init my_init -fini my_fini).

O wspieraniu systemu obie metody, jedną z możliwych korzyści jest to, że kod w .init jest uruchamiany przed .ctors, a kod w .fini po .dtors. Jeśli kolejność jest istotna, jest to co najmniej jeden prosty, ale łatwy sposób na rozróżnienie funkcji init / exit.

Główną wadą jest to, że nie można łatwo mieć więcej niż jedną funkcję _init i jedną funkcję _fini na każdy moduł ładowalny i prawdopodobnie musielibyśmy fragmentować kod w Więcej .so niż motywować. Innym jest to, że przy użyciu opisanej powyżej metody linkera zastępuje się oryginalne funkcje _init i _fini domyślne (dostarczone przez crti.o). W tym miejscu zwykle występują wszelkiego rodzaju inicjalizacje (w Linuksie jest to miejsce, w którym inicjowane jest globalne przypisanie zmiennych). Sposób, który jest opisany tutaj

Zauważ w linku powyżej, że kaskadowe do oryginału {[22] } nie jest potrzebne, ponieważ jest nadal na miejscu. call w inline assembly jest jednak x86-mnemonic i wywołanie funkcji z assembly wyglądałoby zupełnie inaczej dla wielu innych architektury (np. ARM). Kod nie jest przezroczysty.

.init/.fini oraz .ctors/.detors mechanizmy są podobne, ale nie do końca. Kod w .init/.fini działa "jak jest". Czyli można mieć kilka funkcji w .init/.fini, ale trudno jest umieścić je tam w pełni transparentnie w czystym C bez rozbijania kodu w wielu małych plikach .so.

.ctors/.dtors są inaczej zorganizowane niż .init/.fini. .ctors/.dtors sekcje to tylko tabele z wskaźnik do funkcji, a "caller" jest pętlą dostarczaną przez system, która wywołuje każdą funkcję pośrednio. Tzn. loop-caller może być specyficzny dla architektury, ale ponieważ jest częścią systemu (jeśli w ogóle istnieje), nie ma to znaczenia.

Poniższy fragment dodaje nowe wskaźniki funkcji do tablicy funkcji .ctors, zasadniczo tak samo jak robi to __attribute__((constructor)) (metoda może współistnieć z __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;

Można też dodać Wskaźniki funkcji do zupełnie innego wymyślonego przez siebie sekcja. Zmodyfikowany skrypt linkera i dodatkowa funkcja naśladująca loader .ctors/.dtors W takim przypadku potrzebna jest pętla. Ale dzięki niemu można uzyskać lepszą kontrolę nad kolejnością wykonania, dodać in-argument i zwracać kod obsługi e. t. a. (na przykład w projekcie C++, byłoby to przydatne, jeśli potrzebujesz czegoś działającego przed lub po globalnych konstruktorach).

Wolałbym __attribute__((constructor))/((destructor)) tam, gdzie to możliwe, jest to proste i eleganckie rozwiązanie, nawet jeśli wydaje się to oszustwem. Dla koderów bare-metalowych takich jak nie zawsze jest to opcja.

Niektóre dobre odniesienia w książce Linkers & loaders.

 53
Author: Michael Ambrus,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-10 14:57:30

Ta strona zapewnia doskonałe zrozumienie implementacji atrybutów constructor i destructor oraz sekcji wewnątrz ELF, które pozwalają im działać. Po przetrawieniu podanych tutaj informacji, zebrałem trochę dodatkowych informacji i (zapożyczając przykład sekcji od Michaela Ambrusa powyżej) stworzyłem przykład, aby zilustrować pojęcia i pomóc w nauce. Wyniki te przedstawiono poniżej wraz z przykładowym źródłem.

Jak wyjaśniono w tym wątku, constructor i destructor atrybuty tworzą wpisy w sekcji .ctors i .dtors pliku obiektowego. Odniesienia do funkcji można umieszczać w obu sekcjach na jeden z trzech sposobów. (1) używając atrybutu section; (2) constructor i destructor atrybutów lub (3) z wywołaniem inline-assembly (zgodnie z odnośnikiem w odpowiedzi Ambrusa).

Użycie atrybutów constructor i destructor pozwala dodatkowo przypisać priorytet konstruktorowi / destruktorowi, aby kontrolować jego kolejność wykonania przed wywołaniem main() lub po powrocie. Im niższa podana wartość priorytetu, tym wyższy priorytet wykonania (niższe priorytety wykonują się przed wyższymi priorytetami przed main () -- i po wyższych priorytetach po main ()). Wartości priorytetowe, które podajesz muszą być większe niż100 ponieważ kompilator rezerwuje priorytetowe wartości między 0-100 dla implementacji. A constructor lub destructor określone z priorytetem wykonuje przed constructor lub destructor określone bez priorytetu.

Z atrybutem "section" lub za pomocą inline-assembly można również umieszczać odwołania do funkcji w sekcji kodu ELF .init i .fini, które będą wykonywane odpowiednio przed dowolnym konstruktorem i po dowolnym destruktorze. Wszelkie funkcje wywołane przez odwołanie do funkcji umieszczone w sekcji .init, będą wykonywane przed samym odwołaniem do funkcji (jak zwykle).

Starałem się zilustrować każdy z nich w poniższym przykładzie:

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

Wyjście:

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

Przykład pomógł cementować zachowanie konstruktora/destruktora, mam nadzieję, że przyda się również innym.

 29
Author: David C. Rankin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-23 08:09:16

Oto "konkretny" (i prawdopodobnie przydatny) przykład Jak, dlaczego i kiedy używać tych poręcznych, ale nieestetycznych konstrukcji...

Xcode używa "globalnego" "domyślnego użytkownika", aby zdecydować, która klasa XCTestObserver wypluwa serce do oblężonej konsoli.

W tym przykładzie... kiedy domyślnie załaduję tę bibliotekę psuedo, nazwijmy ją... / Align = "center" bgcolor = "# e0ffe0 " / cesarz Chin / / align = center / .

OTHER_LDFLAGS = -ldemure

Chcę za..

  1. Przy obciążeniu (tj. kiedy XCTest ładuje mój pakiet testowy), nadpisuje klasę" default "XCTest" observer"... (poprzez funkcję constructor) PS: z tego co wiem.. wszystko, co tu zrobiono, może być wykonane z równoważnym efektem w metodzie + (void) load { ... } mojej klasy.

  2. Zróbcie testy.... w tym przypadku, z mniejszą szczegółowością w logach (implementacja na żądanie)

  3. Zwraca klasę" global " XCTestObserver do jej nieskazitelnego stanu.. aby nie zepsuć innych / Align = "left" / linked to libdemure.a). Myślę, że historycznie było to zrobione w dealloc.. ale nie zamierzam zadzierać z tą starą wiedźmą.

Więc...

#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

Bez flagi linkera... (Moda-policyjny Rój Cupertino żądający zemsty, jednak domyślne Apple przeważa, Jak jest pożądane, tutaj )

Tutaj wpisz opis obrazka

Z flagą -ldemure.a linkera... (Zrozumiałe wyniki, gasp ... "dzięki constructor/destructor"... Wiwat tłumu ) Tutaj wpisz opis obrazka

 7
Author: Alex Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-09-21 19:34:20

Oto kolejny Beton example.It jest dla wspólnej biblioteki. Główną funkcją biblioteki współdzielonej jest komunikacja z czytnikiem kart inteligentnych. Ale może również odbierać "informacje konfiguracyjne" w czasie wykonywania przez udp. Udp jest obsługiwane przez wątek, który musi być uruchomiony w czasie początkowym.

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

  }

Biblioteka została napisana w c.

 0
Author: drlolly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-17 15:07:42