2010-01-12 61 views
271

這似乎很清楚,它應該設置的東西。__attribute __((構造函數))如何工作?

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

回答

228
  1. 它當一個共享庫被加載運行,通常在程序啓動。
  2. 這就是GCC的所有屬性;大概是爲了將它們與函數調用區分開來。
  3. GCC特有的語法。
  4. 是的,這也適用於C和C++。
  5. 不,該功能不需要是靜態的。
  6. 析構函數在共享庫卸載時運行,通常在程序出口處。

因此,構造函數和析構函數的工作方式是共享目標文件包含特殊部分(ELF上的.ctors和.dtors),它們分別包含對構造函數和析構函數屬性標記的函數的引用。當庫被加載/卸載時,動態加載程序(ld.so或somesuch)會檢查這些段是否存在,如果存在,則調用其中引用的函數。

回想一下,在普通的靜態鏈接器中可能存在一些類似的魔術,因此無論用戶選擇靜態鏈接還是動態鏈接,都會在啓動/關閉時運行相同的代碼。

+36

雙括號使它們很容易「宏觀」('#define __attribute __(x)')。如果你有多個屬性,例如'__attribute __((noreturn,weak))',如果只有一組括號,就很難「宏觀化」。 –

+6

這不是用'.init/.fini'完成的。 (你可以在一個單獨的翻譯單元中有效地擁有多個構造函數和析構函數,從不在多個單獨的庫中 - 這將如何工作?)相反,在使用ELF二進制格式(Linux等)的平臺上,引用構造函數和析構函數在標題的'.ctors'和'.dtors'部分。的確,在過去,名爲'init'和'fini'的函數將在動態庫加載和卸載(如果它們存在)上運行,但現在不推薦使用,而是由此更好的機制取代。 – ephemient

+0

@ephemient:謝謝,我忘記了新的和改進的做事方式。相應地更新答案。 – janneb

47

.init/.fini未被棄用。它仍然是ELF標準的一部分,我敢說這將是永恆的。加載/卸載代碼時,加載器/運行時鏈接器運行代碼.init/.fini。即在每個ELF加載(例如共享庫)代碼.init將運行。仍然可以使用該機制來實現與__attribute__((constructor))/((destructor))大致相同的功能。這是老派,但它有一些好處。

.ctors/.dtors機制例如需要system-rtl/loader/linker-script的支持。這在所有系統上都不可用,例如代碼在裸機上執行的深度嵌入式系統。即即使GDB支持__attribute__((constructor))/((destructor)),也不確定它是否會運行,因爲鏈接器需要組織它以及加載器(或者在某些情況下使用啓動代碼)來運行它。改爲使用.init/.fini,最簡單的方法是使用鏈接器標誌:-init & -fini(即,來自GCC命令行,語法將爲-Wl -init my_init -fini my_fini)。

在系統支持這兩種方法,一個可能的好處是.init代碼是.ctors和代碼前.fini.dtors後運行。如果訂單是相關的,那麼至少有一個原始但簡單的方法可以區分init/exit函數。

一個主要缺點是,你不能輕易擁有每每個加載的模塊不止一個_init和一個_fini功能,並可能會在比激勵更.so進行分段代碼。另一個是,當使用上述連接器方法時,將替換原始的_init和_fini默認功能(由crti.o提供)。這是通常發生各種初始化的地方(在Linux上,這是全局變量賦值初始化的地方)。解決方法如下here

請注意,在上面的鏈接中,不需要級聯到原始_init(),因爲它仍然存在。然而,內聯程序集中的call是x86助記符,並且從程序集調用函數對於許多其他體系結構(例如ARM)看起來完全不同。即代碼不透明。

.init/.fini.ctors/.detors機制是類似的,但不完全相同。 .init/.fini中的代碼按「原樣」運行。即你可以在.init/.fini有幾個函數,但它是AFAIK在語法上很難完全透明地將它們放在純C中,而不會破壞很多小文件中的代碼。

.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; 

人們也可以函數指針添加到一個完全不同的自在這種情況下需要一個修改後的鏈接描述文件和一個模仿加載程序的附加函數,但是通過它可以更好地控制執行順序,添加in-argument和返回代碼處理eta(在C++項目,例如,如果需要在之前運行的某個或某個項目,它將很有用全球構造函數)。

如果可能,我寧願__attribute__((constructor))/((destructor)),這是一個簡單而優雅的解決方案,即使它看起來像是作弊。對於像我這樣的裸機編碼器來說,這並不總是一種選擇。

書中的一些很好的參考文獻Linkers & loaders

24

本頁提供了有關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,您還可以將函數引用放置在分別在任何構造函數之前和任何析構函數之後執行的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 

的例子幫助水泥構造函數/析構函數的行爲,希望這將是有用的人以及。

+0

你在哪裏發現「你給出的優先級值必須大於100」?這些信息在[GCC函數屬性文檔中沒有。](https://gcc.gnu 。org/onlinedocs/gcc/Function-Attributes.html) – Justin

+1

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'試了一下:'warning:從0到100的構造函數優先級保留給實現[默認啓用] void construct0()__attribute__((constructor(99)));'。 –

+0

啊。我用clang嘗試了優先級<100,它似乎在工作,但我的簡單測試用例(一個編譯單元)[太簡單了](http://lists.cs.uiuc.edu/pipermail/llvmbugs/2012-April /022952.html)。 – Justin

6

這裏是一個「具體」(和可能有用的)的例如如何,爲什麼,當使用這些方便的,但難看結構...

Xcode使用「全球」 「用戶默認」來決定哪個XCTestObserver噴出心跳困擾控制檯。

在這個例子中...當我含蓄地在我的測試目標點菜加載此僞庫,我們稱之爲... libdemure.a,通過標誌..

OTHER_LDFLAGS = -ldemure 

我想..

  1. 在負載(即當XCTest加載我的測試包),覆蓋了「默認」 XCTest「觀察者」類...(通過constructor功能)PS:至於我可以告訴..什麼在這裏完成可以在我的班級'01內部完成方法。

  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 

沒有鏈接器標誌...(時裝警察蜂擁而上庫比提諾苛刻的報應,但蘋果的默認盛行,如所希望的,在這裏

enter image description here

WITH -ldemure.a linker fla克...(可理解的結果,喘氣 ... 「感謝constructor/destructor」 ... 人羣歡呼enter image description here

0

這裏是另一個具體example.It爲共享庫。共享庫的主要功能是與智能卡讀卡器進行通信。但它也可以通過udp在運行時接收「配置信息」。 udp由一個線程處理,其中在初始時啓動必須

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

    } 

該圖書館是在c。

+0

如果庫是用C++編寫的,這是一個奇怪的選擇,因爲普通的全局變量構造函數是在C++中運行代碼前的主要代碼。 –

+0

@NicholasWilson這個圖書館實際上是用c語言編寫的。不知道我是如何輸入C++而不是c的。 – drlolly