370 votos

¿Cómo funciona __attribute__((constructor))?

Parece bastante claro que se supone que es para preparar las cosas.

  1. Cuando, exactamente, ¿funcionan?
  2. ¿Por qué hay dos paréntesis?
  3. Es __attribute__ una función? Una macro? La sintaxis?
  4. Funciona en los C? C++?
  5. Hace la función funciona con la necesidad de ser estático?
  6. ¿Cuándo __attribute__((destructor)) de ejecución?

Ejemplo en C Objetivo:

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

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

288voto

janneb Puntos 17303
  1. Se ejecuta cuando una biblioteca compartida que se carga, normalmente durante el arranque del programa.
  2. Que es como todos GCC atributos; presumiblemente para distinguirlas de las llamadas de función.
  3. GCC-sintaxis específica.
  4. Sí.
  5. No.
  6. El destructor se ejecuta cuando la biblioteca compartida se descarga, normalmente a la salida del programa.

Así, la forma en que los constructores y destructores de trabajo es que el objeto compartido archivo contiene secciones especiales (.ctors y .dtors en ELF), que contienen referencias a las funciones marcadas con el constructor y destructor de los atributos, respectivamente. Cuando la biblioteca está cargado/descargado el cargador dinámico programa (ld.so o somesuch) comprueba si tales secciones existen, y si es así, las llamadas de las funciones a las que se hace referencia en el mismo.

Pensándolo bien, probablemente hay algunos similares de magia en el normal estática enlazador, por lo que el mismo código que se ejecute en el inicio/apagado independientemente si el usuario elige estática o dinámica vincular.

65voto

Michael Ambrus Puntos 161

.init/.fini no está obsoleto. Sigue siendo parte del estándar ELF y me atrevería a decir que será para siempre. Código en .init/.fini de ejecutar el loader/runtime linker cuando el código se carga/descarga. Es decir. en cada ELFO de la carga (por ejemplo una biblioteca compartida) de código en .init de su ejecución. Todavía es posible utilizar ese mecanismo para lograr lo mismo como con __attribute__((constructor))/((destructor)). Es de la vieja escuela, pero tiene algunos beneficios.

.ctors/.dtors mecanismo, por ejemplo, requieren de apoyo por el sistema de rtl/loader/linker script. Esto está lejos de estar disponibles en todos los sistemas, por ejemplo profundamente sistemas embebidos, donde el código se ejecuta en el metal desnudo. Es decir. incluso si __attribute__((constructor))/((destructor)) es soportado por GCC, no es cierto que se ejecute como es el vinculador para organizar y para el cargador (o en algunos casos, el código de arranque) para ejecutarlo. Para utilizar .init/.fini en lugar de eso, la forma más fácil es usar linker flags: -init & -fini (es decir. a partir de GCC línea de comandos, la sintaxis sería -Wl -init my_init -fini my_fini).

En el sistema de apoyo de ambos métodos, una posible ventaja es que el código en .init se ejecuta antes de que .ctors y código en .fini después .dtors. Si el pedido es relevante el hecho de que al menos uno crudo, pero la manera fácil de distinguir entre init/funciones de salir.

Una desventaja importante es que usted no puede tener más de un _init y un _fini función por cada módulo cargable y probablemente tendría que fragmento de código en más .so de motivación. Otra es que cuando se utiliza el enlazador método descrito anteriormente, se reemplaza el original _init y _fini funciones predeterminadas (proporcionada por crti.o). Esto es donde todo tipo de inicialización suele ocurrir (en Linux, que es donde global de la asignación de variable es inicializada). Una forma de evitar que se describe aquí

Aviso en el enlace de arriba que una cascada a la original _init() no es necesario, ya que éste está todavía en su lugar. La call en la línea de la asamblea, sin embargo, es x86-tecla de acceso y llamar a una función desde la asamblea sería completamente diferente de muchas otras arquitecturas (como BRAZO por ejemplo). Es decir. código no es transparente.

.init/.fini y .ctors/.detors mecanismos son similares, pero no del todo. Código en .init/.fini corre "como es". Es decir. usted puede tener varias funciones en .init/.fini, pero es AFAIK sintácticamente difícil ponerlas ahí totalmente transparente en C puro sin romper el código en muchos pequeños .so ficheros.

.ctors/.dtors son de diferente manera organizada que .init/.fini. .ctors/.dtors secciones ambos son simplemente tablas con punteros a funciones, y el "visitante" es una proporcionado por el sistema de bucle que llama a cada función de forma indirecta. Es decir. el bucle de llamadas puede ser específico para la arquitectura, pero como es parte del sistema (si es que existe, es decir.) no importa.

El siguiente fragmento de código agrega nuevos punteros de función de la .ctors de la función de la matriz, principalmente, de la misma manera como __attribute__((constructor)) (método puede coexistir con __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;

También se puede agregar la función de punteros a una completamente diferente de auto-inventado sección. Una modificación de la linker script y una función adicional que imitan el cargador .ctors/.dtors bucle que se necesita en este caso. Pero con él se puede lograr un mejor control sobre la ejecución de la orden, agregar en el argumento y devuelve el código de manejo de e.t.a. (En un proyecto de C++, por ejemplo, sería útil si se necesita algo de ejecutar antes o después del mundial de constructores).

Prefiero __attribute__((constructor))/((destructor)) donde sea posible, es una solución sencilla y elegante incluso se siente como si estuviera haciendo trampas. Para bare-metal programadores como yo, esto no es siempre una opción.

Algunos referencia en el libro Enlazadores y cargadores.

40voto

David C. Rankin Puntos 2674

Esta página ofrece una gran comprensión sobre el constructor y destructor atributo de la aplicación y de las secciones dentro de dentro de ELF, que les permiten trabajar. Después de digerir la información proporcionada aquí, he recopilado un poco de información adicional y el (los préstamos de la sección ejemplo de Michael Ambrus arriba) crea un ejemplo para ilustrar los conceptos y ayudar a mi aprendizaje. Los resultados se proporcionan a continuación junto con la fuente de ejemplo.

Como se explica en este hilo, la constructor y destructor atributos de crear entradas en el .ctors y .dtors de la sección del archivo de objeto. Usted puede colocar referencias a funciones, ya sea en la sección en una de tres maneras. (1) el uso de la section atributo; (2) constructor y destructor atributos o (3) con una línea de ensamblaje de la llamada (como la que se hace referencia en el enlace de Ambrus respuesta).

El uso de constructor y destructor atributos permite además asignar una prioridad para el constructor/destructor para el control de su orden de ejecución antes de la main() está llamada o después de que se devuelve. La más baja es la prioridad es el valor, mayor será la prioridad de ejecución (de menor prioridades ejecutar antes de las prioridades de mayor antes de main() -- y posterior a prioridades más altas después de main() ). Los valores de prioridad de dar debe ser mayor que100 como el compilador reserva de prioridad los valores entre 0 y 100 para la aplicación. Unconstructor o destructor especificado con la prioridad que se ejecuta antes de una constructor o destructor especificado sin prioridad.

Con la sección de' atributo o con línea de montaje, también puede colocar las referencias de función en la .init y .fini ELF sección de código que se ejecutará antes de que cualquier constructor y después de cualquier destructor, respectivamente. Las funciones llamadas por la función de referencia se coloca en el .init sección, se ejecutarán antes de que la función de referencia a sí mismo (como de costumbre).

He tratado de ilustrar cada uno de estos en el ejemplo a continuación:

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

salida:

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

El ejemplo que ayudaron a consolidar el constructor/destructor de comportamiento, que espero que sea útil a los demás.

12voto

Quinn Taylor Puntos 29688

Excelente respuesta por @janneb, muy útil. Sólo para la integridad, aquí hay un enlace a los relacionados con GCC docs.

Me alegro de que me tropecé en esta cuestión, sólo me permitió eliminar de dos docenas de líneas de código repetitivo y redundante llamadas de función para hacer algunas biblioteca-ancho de inicialización en mi Objective-C, marco.

NOTA: Aunque esta sintaxis es específico para GCC, LLVM y Ruido han sido escritos para apoyar, y el código compilado utilizando Clang-LLVM parece funcionar sólo como GCC-código compilado.

Iteramos.com

Iteramos es una comunidad de desarrolladores que busca expandir el conocimiento de la programación mas allá del inglés.
Tenemos una gran cantidad de contenido, y también puedes hacer tus propias preguntas o resolver las de los demás.

Powered by:

X