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

Cas*_*ash 327 c c++ gcc objective-c

似乎很清楚它应该设置.

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

目标C中的示例:

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

__attribute__((destructor))
static void destroy_navigationBarImages() {
  [navigationBarImages release];
}
Run Code Online (Sandbox Code Playgroud)

jan*_*neb 265

  1. 它在加载共享库时运行,通常在程序启动期间运行.
  2. 这就是所有GCC属性的方式; 大概是为了区别于函数调用.
  3. GCC特定的语法.
  4. 是的,这也适用于C和C++.
  5. 不,该功能不需要是静态的.
  6. 卸载共享库时,通常在程序退出时运行析构函数.

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

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

  • 双括号使它们很容易"宏出"(`#define __attribute __(x)`).如果你有多个属性,例如`__attribute __((noreturn,weak))`,如果只有一组括号,就很难"宏观". (47认同)
  • @jcayzac不,因为可变参数宏是一个gcc扩展名,并且如果你没有使用gcc,那么宏`出__attribute__`的主要原因是因为它也是一个gcc扩展名. (7认同)
  • @ChrisJester-Young可变参数宏是标准的C99特性,而不是GNU扩展. (7认同)
  • 它不是用`.init/.fini`完成的.(您可以在单个转换单元中有效地拥有多个构造函数和析构函数,在单个库中永远不会多个 - 这将如何工作?)而是在使用ELF二进制格式(Linux等)的平台上,引用构造函数和析构函数在标题的`.ctors`和`.dtors`部分.确实,在过去,名为`init`和`fini`的函数将在动态库加载和卸载时运行(如果它们存在),但现在已弃用,取而代之的是这种更好的机制. (6认同)
  • 仅供参考:在gcc在线文档(http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html)中,它在"构造函数,析构函数"下面说了以下内容:"这些属性当前没有为Objective-C实现". (3认同)
  • @jcayzac C99已经13岁了,但是C++ 11却没有.;-)许多头文件也是为C++兼容性编写的,并且仍然有很多很多程序都是为C++ 98编写的.所以我仍然说现在时的使用仍然是合理的,因为许多程序仍在维护中. (3认同)
  • "你使用现在时("制造"而不是"制造" - 双parens**仍然可以让它们变得容易宏观.你咆哮了错误的迂腐树. (3认同)
  • @ChrisJester-Young:`#define __ attribute __(x,...)`在这种情况下运行良好,没有增加复杂性. (2认同)
  • @jcayzac在C99之前,当第一次发明__attribute__时,请猜测必须支持哪些C编译器。 (2认同)
  • @ ChrisJester-Young当然需要双括号,我没有质疑.我只是想指出你对现在时的使用("make"而不是"made",它是"而不是"它已经"......".C99已经13岁了. (2认同)

Mic*_*rus 60

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

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

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

其主要缺点是,你不能轻易地有不止一个_init,一个_fini每每个可加载模块功能,并可能会在更多的代码片段.so比干劲.另一个是当使用上述链接器方法时,一个替换原始的_init和_fini默认函数(由提供crti.o).这是通常发生各种初始化的地方(在Linux上,这是初始化全局变量赋值的地方).这里描述一种解决方法

请注意,在上面的链接中,_init()不需要级联到原始级别,因为它仍然存在.的call内联组件然而是x86的助记符和呼叫从组件的函数看起来完全不同的许多其它体系结构(如ARM例如).即代码不透明.

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

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

下面的代码片段添加了.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;
Run Code Online (Sandbox Code Playgroud)

还可以将函数指针添加到完全不同的自发明部分.在这种情况下,需要修改的链接描述文件和模仿加载器.ctors/ .dtors循环的附加功能.但有了它,就可以更好地控制执行顺序,添加in-argument和返回代码处理eta(例如,在C++项目中,如果需要在全局构造函数之前或之后运行某些东西,它将非常有用).

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

" 连接器"和"装载器 "一书中有一些很好的参考.


Dav*_*ica 38

本页提供有关重大认识constructordestructor属性的实施和内ELF内,让他们工作的章节.在消化了这里提供的信息之后,我编译了一些额外的信息,并(借用上面Michael Ambrus的部分示例)创建了一个示例来说明概念并帮助我学习.下面提供这些结果以及示例源.

如此线程中所述,constructordestructor属性在目标文件的.ctors.dtors部分中创建条目.您可以使用以下三种方式之一在任一部分中放置对函数的引用.(1)使用任何一个section属性; (2)constructordestructor属性或(3)内联汇编调用(在Ambrus的答案中引用了链接).

使用constructordestructor属性允许您另外为构造函数/析构函数分配优先级,以在main()调用之前或返回之后控制其执行顺序.给定的优先级值越低,执行优先级越高(在main()之前的较高优先级之前执行的优先级较低 - 以及在main()之后的较高优先级之后执行).您提供的优先级值必须大于100编译器保留0-100之间的优先级值才能实现.A constructordestructor具有优先级的指定在没有优先级的情况下执行constructordestructor指定.

随着"部分的"属性或者与内联汇编,你也可以将在函数引用.init.finiELF代码段,将任何构造的任何析构函数之前和之后,分别执行.放置在该.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;
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

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

  • IIRC,有几个引用,[**PATCH:构造函数/析构函数参数支持优先级参数**](https://gcc.gnu.org/ml/gcc-patches/2007-02/msg01950.html) (`MAX_RESERVED_INIT_PRIORITY`),它们与**C++**(`init_priority`)相同[**7.7 C++ - 特定变量,函数和类型属性**](https://gcc.gnu.org /onlinedocs/gcc/C_002b_002b-Attributes.html).然后我用`99`尝试了它:`警告:从0到100的构造函数优先级被保留用于实现[默认启用] void construct0()__ attribute __((constructor(99)));`. (4认同)
  • “静态全局”的效果和可见性取决于程序的结构(例如,单个文件,多个文件(*翻译单元*))以及在其中声明全局的方式。[**请参阅:静态(关键字)** ](https://en.wikipedia.org/wiki/Static_%28keyword%29),尤其是“静态全局变量”说明。 (2认同)

Ale*_*ray 7

这是一个"具体的"(并且可能是有用的)示例,说明如何,为什么以及何时使用这些方便但难看的结构......

Xcode使用"全局""用户默认值"来决定哪个XCTestObserver类向受困的控制台发出它的心脏.

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

OTHER_LDFLAGS = -ldemure
Run Code Online (Sandbox Code Playgroud)

我想要..

  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
Run Code Online (Sandbox Code Playgroud)

没有链接器标志...(时尚警察群Cupertino 要求报复,但苹果默认普遍存在,如期望的那样)

在此输入图像描述

随着-ldemure.a链接器标志......(可理解的结果,喘息 ......"谢谢constructor/ destructor"...... 群众欢呼声) 在此输入图像描述