c++ cy - ¿Cómo funciona exactamente __attribute __((constructor))?




sistema operativo (5)

Parece bastante claro que se supone que debe configurar las cosas.

  1. ¿Cuándo exactamente funciona?
  2. ¿Por qué hay dos paréntesis?
  3. ¿Es __attribute__ una función? Una macro? ¿Sintaxis?
  4. ¿Esto funciona en C? C ++?
  5. ¿La función con la que trabaja necesita ser estática?
  6. ¿Cuándo se __attribute__((destructor)) ?

Ejemplo en Objetivo C :

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

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

Answers

  1. Se ejecuta cuando se carga una biblioteca compartida, generalmente durante el inicio del programa.
  2. Así es como son todos los atributos de GCC; Presumiblemente para distinguirlos de las llamadas a funciones.
  3. Sintaxis específica de GCC.
  4. Sí, esto también funciona en C an C ++.
  5. No, la función no necesita ser estática.
  6. El destructor se ejecuta cuando se descarga la biblioteca compartida, generalmente al salir del programa.

Entonces, la forma en que funcionan los constructores y destructores es que el archivo de objetos compartidos contiene secciones especiales (.ctors y .dtors en ELF) que contienen referencias a las funciones marcadas con los atributos de constructor y destructor, respectivamente. Cuando la biblioteca se carga / descarga, el programa del cargador dinámico (ld.so o somesuch) verifica si existen tales secciones, y si es así, llama a las funciones a las que hace referencia.

Ahora que lo pienso, es probable que haya alguna magia similar en el enlazador estático normal, por lo que el mismo código se ejecuta en el inicio / apagado independientemente de si el usuario elige la vinculación estática o dinámica.


Esta página proporciona un gran entendimiento sobre la implementación de atributos del constructor y destructor y las secciones dentro de ELF que les permiten trabajar. Después de digerir la información proporcionada aquí, compilé un poco de información adicional y (tomando prestado el ejemplo de la sección de Michael Ambrus arriba) creé un ejemplo para ilustrar los conceptos y ayudar a mi aprendizaje. Esos resultados se proporcionan a continuación junto con la fuente de ejemplo.

Como se explica en este hilo, los atributos de constructor y destructor crean entradas en la sección .ctors y .dtors del archivo de objeto. Puede colocar referencias a funciones en cualquiera de las secciones de una de las tres maneras. (1) utilizando el atributo de section ; (2) atributos de constructor y destructor o (3) con una llamada de ensamblaje en línea (como se hace referencia al enlace en la respuesta de Ambrus).

El uso de los atributos de constructor y destructor permite, además, asignar una prioridad al constructor / destructor para controlar su orden de ejecución antes de llamar a main() o después de que regrese. Cuanto menor sea el valor de prioridad dado, mayor será la prioridad de ejecución (las prioridades más bajas se ejecutan antes de las prioridades más altas antes de main () - y las prioridades más altas después de main ()). Los valores de prioridad que proporcione deben ser mayores que 100 ya que el compilador reserva los valores de prioridad entre 0 y 100 para su implementación. Un constructor o destructor especificado con prioridad se ejecuta antes que un constructor o destructor especificado sin prioridad.

Con el atributo 'sección' o con el ensamblaje en línea, también puede colocar referencias de funciones en la sección de código ELF .init y .fini que se ejecutarán antes de cualquier constructor y después de cualquier destructor, respectivamente. Cualquier función llamada por la referencia de función colocada en la sección .init , se ejecutará antes de la referencia de la función (como es habitual).

He tratado de ilustrar cada uno de ellos en el siguiente ejemplo:

#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 ayudó a cimentar el comportamiento del constructor / destructor, con suerte también será útil para otros.


Aquí hay un ejemplo "concreto" (y posiblemente útil ) de cómo, por qué y cuándo usar estas construcciones prácticas pero desagradables ...

Xcode utiliza un "valor predeterminado" por el usuario "global" para decidir qué clase de XCTestObserver arroja su corazón a la consola asediada .

En este ejemplo ... cuando carga implícitamente esta psuedo-library, llamémoslo ... libdemure.a , a través de una bandera en mi objetivo de prueba á la ...

OTHER_LDFLAGS = -ldemure

Quiero..

  1. En la carga (es decir, cuando XCTest carga mi paquete de prueba), anule la XCTest "observador" XCTest "predeterminada" ... (a través de la función de constructor ) PS: Hasta donde puedo decir ... todo lo que se haga aquí se podría hacer con equivalente efecto dentro de mi clase ' + (void) load { ... } método.

  2. ejecutar mis pruebas ... en este caso, con menos verbosidad inane en los registros (implementación a pedido)

  3. Devuelva la clase "global" de XCTestObserver a su estado original ... para no ensuciar otras XCTest que no se hayan subido al carro (también conocido como ligado a libdemure.a ). Supongo que esto se hizo históricamente en dealloc ... pero no voy a empezar a jugar con esa vieja bruja.

Asi que...

#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

Sin la bandera del enlazador ... (El enjambre de la policía de moda Cupertino exige retribución , sin embargo, el valor predeterminado de Apple prevalece, como se desea, aquí )

CON la -ldemure.a bandera de vinculador ... (Resultados comprensibles, jadeo ... "gracias constructor / destructor " ... Aplausos de la multitud )


.init / .fini no está en desuso. Todavía es parte del estándar ELF y me atrevería a decir que será para siempre. El código en .init / .fini es ejecutado por el cargador / runtime-linker cuando el código se carga / descarga. Es decir, en cada carga ELF (por ejemplo, una biblioteca compartida) se ejecutará el código en .init . Aún es posible usar ese mecanismo para lograr casi lo mismo que con __attribute__((constructor))/((destructor)) . Es de la vieja escuela pero tiene algunos beneficios.

.ctors mecanismo de .ctors / .dtors , por ejemplo, requiere soporte de system-rtl / loader / linker-script. Esto dista mucho de estar disponible en todos los sistemas, por ejemplo, sistemas profundamente integrados en los que el código se ejecuta directamente. Por ejemplo, si __attribute__((constructor))/((destructor)) , no es seguro que se ejecute ya que depende del vinculador organizarlo y del cargador (o en algunos casos, del código de inicio) ejecutarlo. Para usar .init / .fini en .fini lugar, la forma más sencilla es usar indicadores de vinculador: -init & -fini (es decir, desde la línea de comandos de GCC, la sintaxis sería -Wl -init my_init -fini my_fini ).

En el sistema que admite ambos métodos, un beneficio posible es que el código en .init se ejecuta antes de .ctors y el código en .fini después de .dtors . Si el orden es relevante, al menos es una forma simple pero fácil de distinguir entre las funciones de inicio / salida.

Un inconveniente importante es que no puede tener fácilmente más de una función _init y una _fini por cada módulo cargable y probablemente tendría que fragmentar el código en más. .so motivado. Otra es que cuando se usa el método de enlace descrito anteriormente, uno reemplaza las funciones predeterminadas _init y _fini originales (proporcionadas por crti.o ). Aquí es donde generalmente se produce todo tipo de inicialización (en Linux es donde se inicializa la asignación de variables globales). Una forma de evitar eso se describe here

Observe en el enlace anterior que no se necesita una conexión en cascada con el _init() original ya que todavía está en su lugar. Sin embargo, la call en el ensamblaje en línea es x86-mnemónica y llamar a una función desde el ensamblaje se vería completamente diferente para muchas otras arquitecturas (como ARM, por ejemplo). Es decir, el código no es transparente.

.init / .fini y .ctors / .detors son similares, pero no del todo. El código en .init / .fini ejecuta "tal cual". Es decir, puede tener varias funciones en .init / .fini , pero AFAIK es sintácticamente difícil de ponerlas de forma totalmente transparente en C pura sin dividir el código en muchos archivos pequeños .so .

.ctors / .dtors están organizados de .init diferente a .init / .fini . .ctors secciones .ctors / .dtors son solo tablas con punteros a funciones, y el "llamador" es un bucle provisto por el sistema que llama a cada función indirectamente. Es decir, el llamador de bucle puede ser específico de la arquitectura, pero como es parte del sistema (si existe, es decir) no importa.

El siguiente fragmento de .ctors agrega nuevos punteros a la función de la matriz de funciones .ctors , principalmente de la misma manera que __attribute__((constructor)) (el 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 pueden agregar los punteros de función a una sección auto-inventada completamente diferente. En tal caso, se necesita un script de vinculador modificado y una función adicional que imite el bucle .ctors / .dtors del cargador. Pero con esto, se puede lograr un mejor control sobre el orden de ejecución, agregar in-argumento y devolver el manejo del código eta (en un proyecto de C ++, por ejemplo, sería útil si se necesita algo que se ejecute antes o después de los constructores globales).

Prefiero __attribute__((constructor))/((destructor)) siempre que sea posible, es una solución simple y elegante, incluso si se siente como un engaño. Para los programadores de metal como yo, esto no es siempre una opción.

Alguna buena referencia en el libro Linkers & loaders .


Si usa mutexes para proteger todos sus datos, realmente no debería preocuparse. Mutexes siempre ha proporcionado suficientes garantías de orden y visibilidad.

Ahora, si usó atomics o algoritmos sin bloqueo, debe pensar en el modelo de memoria. El modelo de memoria describe con precisión cuándo los atómicos proporcionan garantías de ordenación y visibilidad, y proporcionan cercas portátiles para garantías codificadas a mano.

Anteriormente, los métodos atómicos se realizarían mediante el uso de compiladores intrínsecos o alguna biblioteca de nivel superior. Las cercas se habrían hecho usando instrucciones específicas de la CPU (barreras de memoria).





c++ objective-c c gcc