c++ programar Como exatamente o__attribute__((constructor)) funciona?




objective c livro (4)

.init / .fini não está obsoleto. Ainda faz parte do padrão ELF e ouso dizer que será para sempre. O código em .init / .fini é executado pelo carregador / vinculador de tempo de execução quando o código é carregado / descarregado. Ou seja, em cada código de carga ELF (por exemplo, uma biblioteca compartilhada) em .init será executado. Ainda é possível usar esse mecanismo para obter aproximadamente a mesma coisa que com __attribute__((constructor))/((destructor)) . É a velha escola, mas tem alguns benefícios.

.ctors mecanismo .ctors / .dtors , por exemplo, requer suporte pelo script system-rtl / loader / linker-script. Isso está longe de ser garantido em todos os sistemas, por exemplo, sistemas profundamente incorporados onde o código é executado em bare metal. Ou seja, mesmo que o __attribute__((constructor))/((destructor)) seja suportado pelo GCC, não é certo que ele será executado, pois cabe ao vinculador organizá-lo e ao carregador (ou, em alguns casos, ao código de inicialização) executá-lo. Para usar .init / .fini , a maneira mais fácil é usar sinalizadores de linker: -init & -fini (ou seja, da linha de comando do GCC, a sintaxe seria -Wl -init my_init -fini my_fini ).

No sistema que suporta ambos os métodos, um benefício possível é que o código em .init é executado antes de .ctors e código em .fini após .dtors . Se a ordem é relevante, há pelo menos uma maneira bruta mas fácil de distinguir entre as funções de inicialização / saída.

Uma grande desvantagem é que você não pode facilmente ter mais de uma função _init e uma função _fini por cada módulo carregável e provavelmente teria que fragmentar o código em mais do que motivado. Outra é que ao usar o método de vinculador descrito acima, um substitui as funções padrão _init e _fini originais (fornecidas por crti.o ). É onde geralmente ocorrem todos os tipos de inicialização (no Linux, é onde a atribuição de variáveis ​​globais é inicializada). Uma maneira de contornar isso é descrita here

Observe no link acima que uma cascata para o _init() não é necessária, pois ainda está no lugar. A call no assembly inline, no entanto, é x86-mnemônico e chamar uma função do assembly seria completamente diferente para muitas outras arquiteturas (como o ARM, por exemplo). Ou seja, código não é transparente.

.init / .fini e .ctors / .detors são semelhantes, mas não exatamente. O código em .init / .fini é executado "como está". .init seja, você pode ter várias funções em .init / .fini , mas é difícil .fini a AFAIK colocá-las de forma totalmente transparente em C puro, sem dividir o código em muitos arquivos .so pequenos.

.ctors / .dtors são organizados de .init diferente do que .init / .fini . .ctors seções .ctors / .dtors são apenas tabelas com ponteiros para funções, e o "chamador" é um loop fornecido pelo sistema que chama indiretamente cada função. Ou seja, o loop-caller pode ser específico da arquitetura, mas como é parte do sistema (se existir, isto é, não importa).

O snippet a seguir adiciona novos ponteiros de função à matriz de função .ctors , principalmente da mesma maneira que __attribute__((constructor)) does (o método pode coexistir com __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;

Pode-se também adicionar os ponteiros de função a uma seção auto-inventada completamente diferente. Um script de linker modificado e uma função adicional imitando o .ctors loader .ctors / .dtors é necessário nesse caso. Mas com ele pode-se obter melhor controle sobre a ordem de execução, adicionar argumentos e retornar o código de manipulação eta (em um projeto C ++, por exemplo, seria útil se precisar de algo em execução antes ou depois de construtores globais).

Eu prefiro __attribute__((constructor))/((destructor)) que possível, é uma solução simples e elegante, mesmo que pareça uma trapaça. Para codificadores de metal puro como eu, isso nem sempre é uma opção.

Uma boa referência no livro Linkers & loaders .

Parece bastante claro que é suposto para definir as coisas.

  1. Quando exatamente isso funciona?
  2. Por que existem dois parênteses?
  3. __attribute__ é uma função? Uma macro? Sintaxe?
  4. Isso funciona em C? C ++?
  5. A função com a qual trabalha precisa ser estática?
  6. Quando o __attribute__((destructor)) executado?

Exemplo no Objective-C :

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

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

Answer #1

Aqui está um exemplo "concreto" (e possivelmente útil ) de como, por que e quando usar essas construções úteis, mas sem graça ...

O Xcode usa um "padrão de usuário" "global" para decidir qual classe XCTestObserver seu coração para o console sitiado .

Neste exemplo ... quando eu carrego implicitamente esta biblioteca-psuedo, vamos chamá-la ... libdemure.a , via uma flag no meu alvo de teste.

OTHER_LDFLAGS = -ldemure

Eu quero..

  1. Na carga (ou seja, quando o XCTest carrega meu pacote de teste), substitua a classe "padrão" XCTest "observador" ... (através da função constructor ) PS: Tanto quanto eu posso dizer .. qualquer coisa feita aqui poderia ser feita com equivalente efeito dentro da minha classe ' + (void) load { ... } método.

  2. execute meus testes .... neste caso, com menos verbosidade inane nos logs (implementação sob solicitação)

  3. Retorna a classe XCTestObserver "global" para seu estado primitivo .. para não XCTest outras execuções do XCTest que não tenham entrado no bandwagon (também conhecido como. Ligado a libdemure.a ). Eu acho que isso historicamente foi feito em dealloc .. mas eu não estou prestes a começar a mexer com essa velha bruxa.

Assim...

#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

Sem o sinalizador linker ... (Polícia de moda pulula Cupertino exigindo retribuição , mas o padrão da Apple prevalece, como é desejado, aqui )

COM o -ldemure.a linker flag ... (Resultados compreensíveis, gasp ... "obrigado constructor / destructor " ... Multidão felicidades )


Answer #2

Esta página fornece grande compreensão sobre a implementação do constructor e do atributo destructor e as seções dentro do ELF que permitem que funcionem. Depois de digerir as informações fornecidas aqui, eu compilei um pouco de informações adicionais e (tomando emprestado o exemplo da seção de Michael Ambrus acima) criei um exemplo para ilustrar os conceitos e ajudar no meu aprendizado. Esses resultados são fornecidos abaixo junto com a fonte de exemplo.

Conforme explicado neste tópico, os atributos de constructor e destructor criam entradas na seção .ctors e .dtors do arquivo de objeto. Você pode colocar referências a funções em qualquer seção de uma das três maneiras. (1) usando o atributo section ; (2) atributos de constructor e destructor ou (3) com uma chamada de montagem em linha (como referenciado no link na resposta do Ambrus).

O uso de atributos de constructor e destructor permite que você atribua adicionalmente uma prioridade ao construtor / destruidor para controlar sua ordem de execução antes que main() seja chamado ou depois que ele retornar. Quanto menor o valor de prioridade dada, maior a prioridade de execução (prioridades mais baixas são executadas antes de prioridades mais altas antes de main () - e subsequentes a prioridades mais altas após main ()). Os valores de prioridade que você fornecer devem ser maiores que 100 pois o compilador reserva valores de prioridade entre 0 e 100 para implementação. Um constructor ou destructor especificado com prioridade é executado antes de um constructor ou destructor especificado sem prioridade.

Com o atributo 'section' ou com o assembly in-line, você também pode colocar referências de função na seção de código ELF .fini e .fini que serão executadas antes de qualquer construtor e após qualquer destruidor, respectivamente. Quaisquer funções chamadas pela referência de função colocadas na seção .init , serão executadas antes da própria referência da função (como de costume).

Eu tentei ilustrar cada um deles no exemplo abaixo:

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

saída:

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

O exemplo ajudou a consolidar o comportamento do construtor / destruidor, espero que seja útil para os outros também.


Answer #3

Aqui está outro exemplo concreto. É para uma biblioteca compartilhada. A principal função da biblioteca compartilhada é se comunicar com um leitor de cartão inteligente. Mas também pode receber 'informações de configuração' em tempo de execução pelo udp. O udp é manipulado por um thread que DEVE ser iniciado no momento do init.

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

  }

A biblioteca foi escrita em c.





gcc