c++ - sistema - ¿Cómo funciona exactamente__attribute__((constructor))?
swift versions (5)
- Se ejecuta cuando se carga una biblioteca compartida, generalmente durante el inicio del programa.
- Así es como son todos los atributos de GCC; Presumiblemente para distinguirlos de las llamadas a funciones.
- Sintaxis específica de GCC.
- Sí, esto también funciona en C an C ++.
- No, la función no necesita ser estática.
- 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.
Parece bastante claro que se supone que debe configurar las cosas.
- ¿Cuándo exactamente funciona?
- ¿Por qué hay dos paréntesis?
- ¿Es
__attribute__
una función? Una macro? ¿Sintaxis? - ¿Esto funciona en C? C ++?
- ¿La función con la que trabaja necesita ser estática?
- ¿Cuándo se
__attribute__((destructor))
?
__attribute__((constructor))
static void initialize_navigationBarImages() {
navigationBarImages = [[NSMutableDictionary alloc] init];
}
__attribute__((destructor))
static void destroy_navigationBarImages() {
[navigationBarImages release];
}
Aquí hay otro ejemplo concreto. Es para una biblioteca compartida. La función principal de la biblioteca compartida es comunicarse con un lector de tarjetas inteligentes. Pero también puede recibir ''información de configuración'' en tiempo de ejecución sobre udp. El udp es manejado por un hilo que DEBE iniciarse en el momento de inicio.
__attribute__((constructor)) static void startUdpReceiveThread (void) {
pthread_create( &tid_udpthread, NULL, __feigh_udp_receive_loop, NULL );
return;
}
La biblioteca fue escrita en c.
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..
En la carga (es decir, cuando
XCTest
carga mi paquete de prueba), anule laXCTest
"observador"XCTest
"predeterminada" ... (a través de la función deconstructor
) 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.ejecutar mis pruebas ... en este caso, con menos verbosidad inane en los registros (implementación a pedido)
Devuelva la clase "global" de
XCTestObserver
a su estado original ... para no ensuciar otrasXCTest
que no se hayan subido al carro (también conocido como ligado alibdemure.a
). Supongo que esto se hizo históricamente endealloc
... 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 )
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.
.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 .