__attribute __((constructor))はどのように正確に機能しますか?


347

セットアップが必要なことは明らかです。

  1. 正確にはいつ実行されますか?
  2. 括弧が2つあるのはなぜですか?
  3. ある__attribute__機能は?マクロ?構文?
  4. これはCで動作しますか?C ++?
  5. 機能する関数は静的である必要がありますか?
  6. いつ__attribute__((destructor))実行しますか?

Objective-Cの例

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

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

回答:


273
  1. 共有ライブラリが読み込まれると、通常はプログラムの起動時に実行されます。
  2. これがすべてのGCC属性です。おそらくそれらを関数呼び出しと区別するためです。
  3. GCC固有の構文。
  4. はい、これはCおよびC ++で機能します。
  5. いいえ、関数は静的である必要はありません。
  6. デストラクタは、共有ライブラリがアンロードされたときに、通常はプログラムの終了時に実行されます。

したがって、コンストラクタとデストラクタが機能する方法は、共有オブジェクトファイルに、それぞれコンストラクタとデストラクタ属性でマークされた関数への参照を含む特別なセクション(ELFでは.ctorsと.dtors)が含まれていることです。ライブラリがロード/アンロードされると、ダイナミックローダープログラム(ld.soまたはsomesuch)は、そのようなセクションが存在するかどうかを確認し、存在する場合は、そのセクションで参照されている関数を呼び出します。

考えてみてください。ユーザーが静的リンクと動的リンクのどちらを選択したかに関係なく、起動/シャットダウン時に同じコードが実行されるように、通常の静的リンカーにも同様の魔法があると思われます。


49
二重ブラケットにより、「マクロアウト」(#define __attribute__(x))が簡単になります。たとえばのように複数の属性__attribute__((noreturn, weak))がある場合、ブラケットのセットが1つしかないと「マクロアウト」するのが難しくなります。
Chris Jester-Young

7
で完了していません.init/.fini。(1つの翻訳単位に複数のコンストラクタとデストラクタを有効に含めることができ、1つのライブラリに複数を気にしないでください-どのように機能しますか?)代わりに、ELFバイナリ形式を使用するプラットフォーム(Linuxなど)では、コンストラクタとデストラクタが参照されます内.ctors.dtorsヘッダのセクション。確かに、昔は名前が付けられinitfini動的ライブラリのロードとアンロードで実行される関数が存在する場合は実行されていましたが、現在は非推奨であり、この優れたメカニズムに置き換えられています。
ephemient

7
@jcayzacいいえ。可変個のマクロはgcc拡張であり、マクロ化する主な理由は、gcc __attribute__を使用していない場合でもあります。これも、gcc拡張だからです。
Chris Jester-Young

9
@ ChrisJester-Young可変長マクロは標準のC99機能であり、GNU拡張ではありません。
jcayzac

4
『作られた「の代わりに』メイク「現在形(の使用は、」 -二重括弧は、まだマクロうちに簡単にそれらを作るあなたは間違った知識をひけらかすツリーを吠えた。。
ジム・Balter

64

.init/ .finiは非推奨ではありません。それはまだELF標準の一部であり、それは永遠に続くと思います。.init/ 内の.finiコードは、コードがロード/アンロードされるときにloader / runtime-linkerによって実行されます。つまり、ELFロード(共有ライブラリなど)ごとにコード.initが実行されます。そのメカニズムを使用して、とほぼ同じことを実現することはまだ可能 __attribute__((constructor))/((destructor))です。それは古い学校ですが、いくつかの利点があります。

.ctors/ .dtorsメカニズムは、たとえばsystem-rtl / loader / linker-scriptによるサポートを必要とします。これは、コードがベアメタルで実行される深く埋め込まれたシステムなど、すべてのシステムで利用できるとはほど遠いものです。つまり__attribute__((constructor))/((destructor))、GCCでサポートされている場合でも、それを構成するのはリンカー次第であり、それを実行するのはローダー(場合によってはブートコード)なので、実行されるかどうかは不明です。代わりに.init/ を使用するため.finiの最も簡単な方法は、リンカーフラグを使用することです-Wl -init my_init -fini my_fini

両方の方法をサポートするシステムでは、1つの可能な利点は、の.init.ctorsにコードが実行され、.fini後にコードが実行されることです.dtors。順序が関連している場合、それは少なくとも1つの粗雑ですが、init関数とexit関数を区別する簡単な方法です。

主な欠点は、ロード可能なモジュールごとに_init1つ以上の_fini関数を簡単に持つことができず、おそらく.soモチベーションを上げるよりもコードをフラグメント化する必要があることです。もう1つは、上記のリンカーメソッドを使用すると、元の_initおよび_finiデフォルト関数(によって提供されるcrti.o)が置き換えられることです。これは通常、あらゆる種類の初期化が行われる場所です(Linuxでは、これがグローバル変数割り当てが初期化される場所です)。その回避策はここで説明されています

上記のリンクでは、元の_init()場所へのカスケードはまだ残っているため必要ないことに注意してください。callアセンブリがインラインでは、x86-ニーモニック及びアセンブリ(例えば、ARMなど)、他の多くのアーキテクチャで完全に異なって見えることになるから関数を呼び出しています。つまり、コードは透過的ではありません。

.init/ .fini.ctors/の.detorsメカニズムは似ていますが、完全ではありません。.init/のコードは.fini「そのまま」実行されます。つまり、.init/ .finiにいくつかの関数を含めることが.soできますが、多くの小さなファイルのコードを分割せずに純粋なCで完全に透過的にそれらを配置することは、AFAIKの構文上困難です。

.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ループを模倣した追加の関数が必要です。しかし、これを使用すると、実行順序をより適切に制御し、引数内でリターンコードを処理するイータを追加できます(たとえば、C ++プロジェクトでは、グローバルコンストラクターの前または後に何かを実行する必要がある場合に役立ちます)。

__attribute__((constructor))/((destructor))可能な場合は私が好みますが、それはごまかしのように感じても、シンプルでエレガントなソリューションです。私のようなベアメタルプログラマーにとって、これは必ずしも選択肢ではありません。

リンカーとローダー』という本の良い参考文献。


ローダーはこれらの関数をどのように呼び出すことができますか?これらの関数は、プロセスアドレス空間でグローバルやその他の関数を使用できますが、ローダーは独自のアドレス空間を持つプロセスです。
user2162550

@ user2162550いいえ、ld-linux.so.2(通常の「インタプリタ」、動的にリンクされたすべての実行可能ファイルで実行される動的ライブラリのローダー)は、実行可能ファイル自体のアドレス空間で実行されます。一般に、ダイナミックライブラリローダー自体はユーザー空間に固有のものであり、ライブラリリソースにアクセスしようとするスレッドのコンテキストで実行されます。
Paul Stelian

__attribute__((constructor))/((destructor))デストラクタがあるコードからexecv()を呼び出すと、実行されません。上記のように、.dtorにエントリを追加するなど、いくつかのことを試しました。しかし、成功しません。問題はnumactlでコードを実行することで簡単に再現できます。たとえば、test_codeにデストラクタが含まれていると仮定します(printfをコンストラクタに追加し、desctructor関数で問題をデバッグします)。次にを実行しLD_PRELOAD=./test_code numactl -N 0 sleep 1ます。コンストラクターは2回呼び出されますが、デストラクターは1回しか呼び出されないことがわかります。
B Abali

39

このページでは、constructorおよびdestructor属性の実装と、ELF内で機能するためのELF内のセクションについて詳しく説明しています。ここで提供された情報を要約した後、私は追加の情報を少しまとめて(上記のMichael Ambrusからのセクションの例を借用して)概念を説明し、私の学習に役立つ例を作成しました。これらの結果は、サンプルのソースとともに以下に提供されています。

このスレッドで説明したように、constructorおよびdestructor属性は、オブジェクトファイルの.ctorsおよび.dtorsセクションにエントリを作成します。関数への参照は、3つの方法のいずれかでいずれかのセクションに配置できます。(1)section属性を使用する。(2)constructorおよびdestructor属性、または(3)インラインアセンブリ呼び出し(Ambrusの回答のリンクを参照)。

使用constructordestructor属性を使用すると、さらに前に、その実行順序を制御するために、コンストラクタ/デストラクタに優先順位を割り当てることができmain()呼び出されるか、それが戻った後。指定された優先度の値が低いほど、実行優先度が高くなります(main()の前に高い優先度の前に実行され、main()の後に高い優先度の後に実行されます)。指定する優先度の値は、コンパイラーが実装のために0〜100の優先度の値を予約するため、より大きくなければなりません100。優先度付きのconstructorまたdestructorはは、優先度なしのconstructorまたはの前に実行destructorされます。

'section'属性を使用するか、インラインアセンブリを使用すると、関数参照.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;
}

出力:

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

この例は、コンストラクタ/デストラクタの動作を固めるのに役立ちました。うまくいけば、他の人にも役立つでしょう。


「優先順位の値は100より大きい必要がある」とどこでわかりましたか。その情報は、GCC関数の属性のドキュメントには記載さ
ジャスティン

4
IIRC、参照が2つありました。パッチ:コンストラクタ/デストラクタの引数MAX_RESERVED_INIT_PRIORITY)の優先度引数をサポートし、それらはC ++init_priority7.7 C ++固有の変数、関数、および型属性と同じでした。そして、私はそれを試してみました99warning: constructor priorities from 0 to 100 are reserved for the implementation [enabled by default] void construct0 () __attribute__ ((constructor (99)));
David C.Rankin

1
ああ。clangで100未満の優先度を試しましたが、機能しているように見えましたが、私の単純なテストケース(単一のコンパイルユニット)は単純すぎました
ジャスティン

1
静的グローバル変数(静的ctor)の優先順位は何ですか?
ダッシュ08

2
静的グローバルの効果と可視性は、プログラムの構造(例:単一ファイル、複数ファイル(翻訳単位))とグローバルが宣言されている場所によって異なります。静的(キーワード)を参照してください。具体的には、静的グローバル変数の説明です。
David

7

以下は、これらの便利でありながら見苦しい構造をどのように、なぜ、そしていつ使用するかについての「具体的な」(そしておそらくは有用な)例です...

Xcodeはどの決定する「グローバル」「ユーザーデフォルト」を使用してXCTestObserver、クラスそれの心を吐き出す包囲されたコンソール。

この例では...この疑似ライブラリを暗黙的にロードするときにlibdemure.a、テストターゲットアラのフラグを使用して呼び出します...

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

リンカーフラグがない場合...(報復を要求するファッションポリスがクパチーノに集結ますが、必要に応じて、ここでは Appleのデフォルトが優先されます。

ここに画像の説明を入力してください

リンカフラグを使用して-ldemure.a...(わかりやすい結果、gasp ... "ありがとうconstructor/ destructor" ... 群衆は乾杯ここに画像の説明を入力してください


1

これは別の具体的な例です。共有ライブラリ用です。共有ライブラリの主な機能は、スマートカードリーダーと通信することです。ただし、実行時にudpを介して「構成情報」を受け取ることもできます。udpは、初期化時に開始する必要があるスレッドによって処理されます。

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

  }

ライブラリはcで書かれました。


1
ライブラリがC ++で記述されている場合は奇妙な選択です。通常のグローバル変数コンストラクターは、C ++でプリメインコードを実行する慣用的な方法だからです。
Nicholas Wilson

@NicholasWilsonライブラリは実際にはcで書かれています。cの代わりにc ++と入力した方法がわかりません。
drlolly
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.