【问题标题】:How exactly does __attribute__((constructor)) work?__attribute__((constructor)) 究竟是如何工作的?
【发布时间】:2011-01-04 09:54:16
【问题描述】:

似乎很清楚,它应该进行设置。

  1. 具体什么时候运行?
  2. 为什么有两个括号?
  3. __attribute__ 是一个函数吗?宏?语法?
  4. 这在 C 语言中有效吗? C++?
  5. 它使用的函数是否需要是静态的?
  6. __attribute__((destructor)) 何时运行?

Example in Objective-C:

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

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

【问题讨论】:

    标签: c++ objective-c c gcc


    【解决方案1】:

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

    正如该线程中所解释的,constructordestructor 属性在目标文件的 .ctors.dtors 部分中创建条目。您可以通过以下三种方式之一在任一部分中放置对函数的引用。 (1) 使用section 属性之一; (2) constructordestructor 属性或 (3) 带有内联汇编调用(如 Ambrus 回答中的链接所引用)。

    使用constructordestructor 属性允许您在调用main() 之前或返回之后为构造函数/析构函数额外分配优先级以控制其执行顺序。给定的优先级值越低,执行优先级越高(低优先级在 main() 之前的高优先级之前执行 - 并且在 main() 之后的高优先级之后执行)。您提供的优先级值必须大于100,因为编译器会保留 0-100 之间的优先级值以供实施。指定优先级的constructordestructor 在指定无优先级的constructordestructor 之前执行。

    使用 'section' 属性或内联汇编,您还可以将函数引用放置在 .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
    

    该示例有助于巩固构造函数/析构函数的行为,希望它对其他人也有用。

    【讨论】:

    【解决方案2】:

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

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

    该库是用 C 编写的。

    【讨论】:

      【解决方案3】:
      1. 它在加载共享库时运行,通常是在程序启动期间。
      2. 这就是所有 GCC 属性的方式;大概是为了将它们与函数调用区分开来。
      3. GCC 特定语法。
      4. 是的,这适用于 C 和 C++。
      5. 不,函数不需要是静态的。
      6. 析构函数在共享库卸载时运行,通常在程序退出时运行。

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

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

      【讨论】:

      • 双括号使它们很容易“放大”(#define __attribute__(x))。如果您有多个属性,例如__attribute__((noreturn, weak)),那么如果只有一组括号,就很难“宏化”。
      • .init/.fini 没有完成。 (您可以在一个翻译单元中有效地拥有多个构造函数和析构函数,更不用说在一个库中拥有多个——这将如何工作?)相反,在使用 ELF 二进制格式(Linux 等)的平台上,构造函数和析构函数被引用在标题的 .ctors.dtors 部分中。诚然,在过去,名为 initfini 的函数将在动态库加载和卸载时运行(如果它们存在),但现在已弃用,取而代之的是这种更好的机制。
      • @jcayzac 不,因为可变参数宏是 gcc 扩展,而将 __attribute__ 宏化的主要原因是如果您不使用 gcc,因为那也是 gcc 扩展。
      • @ChrisJester-Young 可变参数宏是标准 C99 功能,而不是 GNU 扩展。
      • "你使用现在时(“make”而不是“made”——双括号仍然让它们很容易被宏化。你叫错了迂腐的树.
      【解决方案4】:

      这是一个“具体”(并且可能有用)示例,说明如何、为什么以及何时使用这些方便但不美观构造...

      Xcode 使用“全局”“用户默认值”来决定哪个 XCTestObserver将它的心脏输出陷入困境的控制台。

      在这个例子中...当我隐式加载这个伪库时,我们称它为...libdemure.a,通过我的测试目标中的一个标志 á la..

      OTHER_LDFLAGS = -ldemure
      

      我想……

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

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

      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, 
          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
      

      没有链接器标志...(时尚警察群 Cupertino 要求报复,但 Apple 的默认设置占上风,如愿以偿,在这里

      带有-ldemure.a 链接器标志...(可理解的结果,喘气...“感谢constructor/destructor”...人群欢呼 )

      【讨论】:

        【解决方案5】:

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

        .ctors/.dtors 机制例如需要 system-rtl/loader/linker-script 的支持。这远不能确定在所有系统上都可用,例如代码在裸机上执行的深度嵌入式系统。 IE。即使 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 助记符,并且从程序集中调用函数对于许多其他体系结构(例如 ARM)来说看起来完全不同。 IE。代码不透明。

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

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

        下面的 sn-p 将新的函数指针添加到.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 循环的附加函数。但是有了它,可以更好地控制执行顺序,添加参数和返回代码处理等。 (例如,在 C++ 项目中,如果需要在全局构造函数之前或之后运行某些东西,这将很有用)。

        如果可能,我更喜欢__attribute__((constructor))/((destructor)),这是一个简单而优雅的解决方案,即使感觉像是在作弊。对于像我这样的裸机程序员来说,这并不总是一种选择。

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

        【讨论】:

        • 加载程序如何调用这些函数?那些函数可以在进程地址空间中使用全局变量和其他函数,但是loader是一个拥有自己地址空间的进程,不是吗?
        • @user2162550 不,ld-linux.so.2(通常的“解释器”,在所有动态链接的可执行文件上运行的动态库的加载器)在可执行文件本身的地址空间中运行。一般来说,动态库加载器本身是特定于用户空间的,在尝试访问库资源的线程的上下文中运行。
        • 当我从具有__attribute__((constructor))/((destructor)) 的代码中调用 execv() 时,析构函数不会运行。我尝试了一些事情,例如向 .dtor 添加条目,如上所示。但没有成功。通过使用 numactl 运行代码很容易复制问题。例如,假设 test_code 包含析构函数(在构造函数和析构函数中添加 printf 以调试问题)。然后运行LD_PRELOAD=./test_code numactl -N 0 sleep 1。你会看到构造函数被调用了两次,而析构函数只被调用了一次。
        猜你喜欢
        • 2011-06-26
        • 2021-08-15
        • 2012-06-08
        • 2011-10-11
        • 2013-07-05
        • 1970-01-01
        • 1970-01-01
        • 2014-09-29
        • 2011-06-01
        相关资源
        最近更新 更多