c++ - site - swift programming language




Como exatamente o__attribute__((constructor)) funciona? (4)

  1. Ele é executado quando uma biblioteca compartilhada é carregada, geralmente durante a inicialização do programa.
  2. É assim que todos os atributos do GCC são; presumivelmente para distingui-los das chamadas de função.
  3. Sintaxe específica do GCC.
  4. Sim, isso também funciona em C e C ++.
  5. Não, a função não precisa ser estática.
  6. O destruidor é executado quando a biblioteca compartilhada é descarregada, normalmente na saída do programa.

Portanto, a maneira como os construtores e destrutores trabalham é que o arquivo de objeto compartilhado contém seções especiais (.ctors e .dtors on ELF) que contêm referências às funções marcadas com os atributos de construtor e destrutor, respectivamente. Quando a biblioteca é carregada / descarregada, o programa do carregador dinâmico (ld.so ou alguma coisa) verifica se essas seções existem e, em caso afirmativo, chama as funções nele referenciadas.

Venha para pensar sobre isso, provavelmente há alguma mágica semelhante no vinculador estático normal, para que o mesmo código seja executado na inicialização / desligamento, independentemente de o usuário escolher vinculação estática ou dinâmica.

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

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.


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.


.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 .





gcc