web-dev-qa-db-ja.com

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

それは物事を設定することになっていることはかなり明らかなようです。

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

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

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

257
janneb

.init/.finiは非推奨ではありません。それはまだELF標準の一部であり、私はそれが永遠に続くことを敢えて言うだろう。 .init/.finiのコードは、コードのロード/アンロード時にローダー/ランタイムリンカーによって実行されます。つまり各ELFロード(共有ライブラリなど)で.initのコードが実行されます。 __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になります)。

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

主な欠点は、ロード可能なモジュールごとに複数の_init関数と1つの_fini関数を簡単に作成できないことであり、おそらく動機付けよりも多くの.soでコードをフラグメント化する必要があることです。もう1つは、上記のリンカーメソッドを使用する場合、元の_initおよび_finiデフォルト関数(crti.oで提供)を置き換えることです。これは通常、あらゆる種類の初期化が行われる場所です(Linuxでは、グローバル変数の割り当てが初期化される場所です)。それを回避する方法は here

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

.init/.fini.ctors/.detorsのメカニズムは似ていますが、完全ではありません。 .init/.finiのコードは「現状のまま」実行されます。つまり.init/.finiに複数の関数を含めることができますが、多くの小さな.soファイルのコードを分割せずに純粋な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;

完全に異なる自己発明セクションに関数ポインターを追加することもできます。このような場合、変更されたリンカースクリプトと、ローダー.ctors/.dtorsループを模倣する追加関数が必要です。しかし、それを使用すると、実行順序をより適切に制御し、引数を追加して、コード処理などを返すことができます。 (たとえば、C++プロジェクトでは、グローバルコンストラクターの前または後に何かを実行する必要がある場合に役立ちます)。

可能な場合は__attribute__((constructor))/((destructor))を好みます。それは不正行為のように感じても、シンプルでエレガントなソリューションです。私のようなベアメタルコーダーにとって、これは常にオプションとは限りません。

本のいくつかの良いリファレンス リンカーとローダー .

60
Michael Ambrus

このページでは、constructorおよびdestructor属性の実装と、それらを機能させるELF内のセクションについて十分に理解できます。ここで提供された情報を消化した後、少しの追加情報をコンパイルし(上記のMichael Ambrusのセクションの例を借りて)、概念を説明し、学習に役立つ例を作成しました。これらの結果は、サンプルのソースとともに以下に提供されます。

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

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

'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

この例は、コンストラクタ/デストラクタの動作を強化するのに役立ちました。他の人にも役立つことを願っています。

34
David C. Rankin

以下に、これらの便利な、しかし見苦しい構造を使用するhow、why、whenの「具体的な」(およびおそらく便利な)の例を示します...

Xcodeは「グローバル」「ユーザーデフォルト」を使用して、どのXCTestObserverクラスがハートアウトbereagueredconsoleに吐き出すかを決定します。

この例では...この擬似ライブラリを暗黙的にロードするときに、テスト対象のフラグを介して... libdemure.aを呼び出しましょう。

OTHER_LDFLAGS = -ldemure

したい..

  1. ロード時(つまり、XCTestがテストバンドルをロードするとき)、「デフォルト」XCTest「observer」クラスをオーバーライドします...(constructor関数を介して)クラスの+ (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のデフォルトが優先、望まれるように、ここで

enter image description here

WITH -ldemure.aリンカーフラグ...(わかりやすい結果、gasp... "thanks constructor/destructor" ... Crowd cheers) enter image description here

7
Alex Gray

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

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

  }

ライブラリはcで作成されました。

1
drlolly