__attribute __((构造函数))如何工作?

这似乎很清楚,它应该设置的东西。

  1. 什么时候运行?
  2. 为什么有两个括号?
  3. __attribute__是一个函数吗? macros? 句法?
  4. 这在C中工作吗? C ++?
  5. 它的function需要是静态的吗?
  6. 什么时候__attribute__((destructor))运行?

目标C中的示例 :

 __attribute__((constructor)) static void initialize_navigationBarImages() { navigationBarImages = [[NSMutableDictionary alloc] init]; } __attribute__((destructor)) static void destroy_navigationBarImages() { [navigationBarImages release]; } 
  1. 它在加载共享库时运行,通常在程序启动过程中。
  2. 这就是GCC的所有属性。 大概是为了区别于函数调用。
  3. GCC特定的语法。
  4. 是的,这也适用于C和C ++。
  5. 不,该function不需要是静态的。
  6. 卸载共享库时,通常在程序出口处运行析构函数。

因此,构造函数和析构函数的工作方式是共享目标文件包含特殊部分(ELF中的.ctors和.dtors),它们分别包含对构造函数和析构函数属性标记的函数的引用。 当加载/卸载程序库时,dynamic加载程序(ld.so或somesuch)会检查这些段是否存在,如果存在,调用其中引用的函数。

想想看,在普通的静态链接器中可能有一些类似的魔术,所以无论用户select静态链接还是dynamic链接,都会在启动/closures时运行相同的代码。

.init / .fini不被弃用。 它仍然是ELF标准的一部分,我敢说这将是永恒的。 当代码被加载/卸载时, .init / .fini代码由加载器/运行时链接器运行。 即在每个ELF加载(例如一个共享库)代码在.init将运行。 仍然有可能使用该机制来实现与__attribute__((constructor))/((destructor)) 。 这是老派,但它有一些好处。

.ctors / .dtors机制,例如需要system-rtl / loader / linker-script的支持。 这在所有系统上都是不可靠的,例如代码在裸机上执行的深度embedded式系统。 即使__attribute__((constructor))/((destructor))被GCC支持,也不能肯定它会运行,因为它由链接器来组织和加载器(或在某些情况下,启动代码)运行。 要使用.init / .fini代替,最简单的方法是使用链接器标志:-init&-fini(即从GCC命令行,语法将是-Wl -init my_init -fini my_fini )。

在支持这两种方法的系统上,一个可能的好处是.init中的代码在.fini之前运行,在.fini之后在.fini之后.dtors 。 如果顺序是相关的,至less有一个原始的,但简单的方法来区分init / exit函数。

一个主要的缺点是你不能轻易地为每个可载入模块使用多个_init和一个_fini函数,并且可能必须将代码片段.so成更多的动机。 另一个是,当使用上面描述的链接器方法时,将replace原始的_init和_fini默认函数(由crti.o提供)。 这是通常发生各种初始化的地方(在Linux上这是全局variables赋值初始化的地方)。 这里介绍一种解决方法

注意在上面的链接中,不需要级联到原始的_init() ,因为它仍然在位。 然而,内联程序集中的call是x86助记符,从程序集中调用函数对于许多其他体系结构(例如ARM)看起来完全不同。 即代码不透明。

.init / .fini.ctors / .detors机制类似,但不完全相同。 .init / .fini代码按“原样”运行。 也就是说,你可以在.init / .fini有几个函数,但是AFAIK语法上很难在纯C中完全透明地将它们放在那里,而不会破坏许多小的.so文件中的代码。

.ctors / .dtors.init / .fini.ctors / .dtors部分都只是指向函数的表,“调用者”是系统提供的循环,间接调用每个函数。 也就是说,循环调用者可以是体系结构特定的,但是因为它是系统的一部分(如果它全部存在的话)并不重要。

下面的片段为.ctors函数数组添加了新的函数指针,主要和__attribute__((constructor))一样(方法可以和__attribute__((constructor)))共存__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; 

也可以将函数指针添加到一个完全不同的自创部分。 在这种情况下,需要修改链接器脚本和模拟加载器.ctors / .dtors循环的附加函数。 但是用它可以更好地控制执行顺序,添加in-argument和返回代码处理eta(例如,在一个C ++项目中,如果需要在全局构造函数之前或之后运行的东西,这将是有用的)。

我更喜欢__attribute__((constructor))/((destructor)) ,这是一个简单而优雅的解决scheme,即使它看起来像是作弊。 对于像我这样的裸机编码器来说,这并不总是一种select。

在书籍连接器和装载机一些很好的参考。

本页面提供了有关constructordestructor属性实现以及ELF内允许它们工作的部分的很好理解。 在提供了这里提供的信息之后,我汇编了一些额外的信息(借用上面的Michael Ambrus的部分示例)创build了一个例子来说明概念并帮助我学习。 下面提供了这些结果以及示例源。

正如此线程中所解释的, constructordestructor属性在对象文件的.ctors.dtors部分中创build条目。 您可以通过三种方法之一将参考放在任一部分的函数中。 (1)使用section属性; (2) constructordestructor属性,或(3)内联程序集调用(参考Ambrus的答案中的链接)。

constructordestructor的使用允许你在调用main()之前或返回之后,为构造函数/析构函数额外地分配一个优先级来控制它的执行顺序。 给定的优先级值越低,执行优先级越高(在main()之前的较高优先级之前执行的较低优先级 – 在main()之后的较高优先级之后执行)。 您给出的优先级值必须大于100因为编译器会保留0-100之间的优先级值以供实施。 用优先级指定的constructordestructor没有优先级指定的constructordestructor之前执行。

通过'section'属性或者inline-assembly,你也可以把函数引用放在.init.fini ELF代码段中,它们分别在任何构造函数之前和任何析构函数之后执行。 放在.init部分中的函数引用所调用的任何函数都将在函数引用之前执行(如往常一样)。

我试图说明下面的例子中的每一个:

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

输出:

 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 

这个例子帮助巩固了构造器/析构器的行为,希望对其他人也有用。

这是一个“具体的”(也可能是有用的 )例子, 说明如何,为什么以及何时使用这些方便而又难看的结构。

Xcode使用“全局”“用户默认”来决定哪个XCTestObserver类将它的心脏传递给处于困境的控制台。

在这个例子中…当我隐式加载这个伪文库时,我们把它称为… libdemure.a ,通过我的testing目标中的一个标志。

 OTHER_LDFLAGS = -ldemure 

我要..

  1. 在加载时(即,当XCTest加载我的testing包时),重写“默认” XCTest “观察者”类…(通过constructor函数)PS:据我所知,在这里完成的任何事情都可以用等价效果在我的class级+ (void) load { ... }方法。

  2. 运行我的testing….在这种情况下,在日志中更less的冗长(根据请求实现)

  3. 将“全局” XCTestObserver类返回到原始状态,以免其他XCTest运行(没有链接到libdemure.a )。 我想这在历史上是dealloc完成的..但我不会开始搞乱那个老巫婆。

所以…

 #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, ie "@"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 

没有链接标志…(时尚警察群体库比蒂诺要求报复 ,但苹果的默认优势, 如所期望的,在这里

在这里输入图像描述

-ldemure.a链接器标志…(可理解的结果, 喘气 …“谢谢constructor / destructor ”… 人群欢呼声在这里输入图像描述

这是另一个具体的例子。它是一个共享库。 共享库的主要function是与智能卡读卡器通信。 但它也可以通过udp在运行时接收“configuration信息”。 udp由一个线程处理,它必须在init时启动。

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

该库是用c ++编写的。