__attribute __((构造函数))究竟是如何工作的?

时间:2010-01-12 22:43:05

标签: c++ objective-c c gcc

似乎很清楚它应该设置好。

  1. 什么时候运行?
  2. 为什么有两个括号?
  3. __attribute__是一个功能吗?一个宏?语法?
  4. 这是否适用于C? C ++?
  5. 它使用的功能是否需要是静态的?
  6. __attribute__((destructor))何时运行?
  7. Example in Objective-C

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

5 个答案:

答案 0 :(得分:257)

  1. 在加载共享库时运行,通常在程序启动期间运行。
  2. 这就是GCC的所有属性;大概是为了区别于函数调用。
  3. GCC特定的语法。
  4. 是的,这适用于C和C ++。
  5. 不,该功能不需要是静态的。
  6. 析构函数在卸载共享库时运行,通常在程序退出时运行。
  7. 因此,构造函数和析构函数的工作方式是共享对象文件包含特殊部分(ELF上的.ctors和.dtors),它们分别包含对使用构造函数和析构函数属性标记的函数的引用。当加载/卸载库时,动态加载程序(ld.so或somesuch)会检查这些部分是否存在,如果存在,则调用其中引用的函数。

    想想看,在普通的静态链接器中可能存在一些类似的魔法,因此无论用户选择静态链接还是动态链接,都会在启动/关闭时运行相同的代码。

答案 1 :(得分:60)

.init / .fini未被弃用。它仍然是ELF标准的一部分,我敢说它会永远存在。加载/卸载代码时,.init / .fini中的代码由loader / runtime-linker运行。即在每个ELF加载(例如共享库)上将运行.init中的代码。仍然可以使用该机制实现与__attribute__((constructor))/((destructor))大致相同的功能。这是老派,但它有一些好处。

例如,

.ctors / .dtors机制需要system-rtl / loader / linker-script的支持。这远非确定在所有系统上都可用,例如深度嵌入式系统,其中代码在裸机上执行。即即使GCC支持__attribute__((constructor))/((destructor)),也不确定它是否会运行,因为它由链接器来组织它以及加载器(或在某些情况下,引导代码)来运行它。要使用.init / .fini,最简单的方法是使用链接器标志:-init& -fini(即来自GCC命令行,语法为-Wl -init my_init -fini my_fini)。

在支持这两种方法的系统上,一个可能的好处是.init中的代码在.ctors之前运行,.fini中的代码在.dtors之后运行。如果订单是相关的,那么至少是一种粗略但简单的方法来区分初始/退出函数。

一个主要缺点是,每个可加载模块不能轻易拥有多个_init和一个_fini函数,而且可能需要将代码分割为更多.so而不是动机。另一个是当使用上述链接器方法时,可以替换原始的_init和_fini默认函数(由crti.o提供)。这是通常发生各种初始化的地方(在Linux上,这是初始化全局变量赋值的地方)。解决这个问题的方法是here

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

.init / .fini.ctors / .detors机制相似,但并不完全相同。 .init / .fini中的代码“按原样”运行。即您可以在.init / .fini中使用多个函数,但在语法上AFAIK很难将它们完全透明地放在纯C中,而不会在许多小.so个文件中分解代码。

.ctors / .dtors.init / .fini的组织方式不同。 .ctors / .dtors部分都只是带有函数指针的表,而“调用者”是系统提供的循环,它间接调用每个函数。即循环调用者可以是特定于体系结构的,但是因为它是系统的一部分(如果它存在的话),这并不重要。

以下代码段添加了.ctors函数数组的新函数指针,主要与__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和返回代码处理e.t.a. (例如,在C ++项目中,如果需要在全局构造函数之前或之后运行某些东西,它将非常有用)。

在可能的情况下,我更喜欢__attribute__((constructor))/((destructor)),这是一个简单而优雅的解决方案,即使它感觉像作弊。对于像我这样的裸机编码器,这并不总是一种选择。

本书Linkers & loaders中的一些很好的参考资料。

答案 2 :(得分:34)

此页面提供了对constructordestructor属性实现以及ELF中允许它们工作的部分的深入理解。在消化了这里提供的信息之后,我编译了一些额外的信息,并(借用上面Michael Ambrus的部分示例)创建了一个示例来说明概念并帮助我学习。下面提供了这些结果以及示例源。

如此线程所述,constructordestructor属性在目标文件的.ctors.dtors部分创建条目。您可以使用以下三种方式之一在任一部分中放置对函数的引用。 (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

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

答案 3 :(得分:7)

这是一个“具体”(和可能有用的如何,为什么以及何时使用这些方便,但难看的的例子构建...

Xcode使用“全局”“用户默认值”来决定哪个XCTestObserver beleaguered 控制台发送

在这个例子中......当我隐式加载这个伪文库时,让我们通过我的测试目标中的一个标志来调用它...... libdemure.a ..

OTHER_LDFLAGS = -ldemure

我想..

  1. 在加载时(即当XCTest加载我的测试包时),覆盖“默认”XCTest“观察者”类...(通过constructor函数) PS:据我所知,这里完成的任何事情都可以在我的班级+ (void) load { ... }方法中以相同的效果完成。

  2. 运行我的测试......在这种情况下,日志中的冗余程度较低(根据要求实施)

  3. 将“全局”XCTestObserver类返回到它的原始状态..以免弄乱其他XCTest没有加入潮流的运行(又名。链接到{ {1}})。我想这在历史上是在libdemure.a完成的......但是我不打算开始搞乱这个老巫婆。

  4. 因此...

    dealloc

    没有链接器标志......(时尚警察群Cupertino 要求报复,但苹果默认优先,按需要,这里

    enter image description here

    使用#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 链接器标记...(可理解的结果,喘气 ...“感谢-ldemure.a / constructor”... 人群欢呼enter image description here

答案 4 :(得分:1)

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

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

  }

该库是用c。

编写的