2005年12月17日

main() の前に関数を呼ぶ

C/C++ のプログラムで、main() の前に関数を暗黙的に呼びたいときがあります。ここでは GCC の拡張を使った方法と、C++ のコンストラクタを使った方法を紹介したいと思います。

 

GCC では main() の前に呼ばれる関数を __attribute__((constructor)) という拡張機能を使って定義できます。たとえば、次のプログラムでは main() の前に foo() が呼び出されます。

#include <stdio.h>

__attribute__((constructor))
void
foo()
{
    printf("hello, before main\n");
}

int
main (int argc, char **argv)
{
    printf("hello, world\n");
    return 0;
}

実行結果は以下の通りです。

% ./a.out
hello, before main
hello, world

__attribute__((constructor)) は GCC の拡張機能であるため移植性がなくなります。GCC のその他の関数属性については GNU コンパイラ集(GCC) の使い方と移植についてが参考になります。

一方、 C++ ではクラスのコンストラクタを使って、同様のことが移植可能な方法で行えます。次のプログラムでは無名の名前空間の中で foo_caller というクラスのコンストラクタで foo() を呼ぶようにしています。foo_caller クラスのオブジェクト caller が作られると foo_caller のコンストラクタが呼ばれて foo() が呼ばれる仕組みです。

#include <stdio.h>

void
foo()
{
    printf("hello, before main\n");
}
namespace { struct foo_caller { foo_caller() { foo(); } } caller; }

int
main (int argc, char **argv)
{
    printf("hello, world\n");
    return 0;
}

ライブラリの場合

光成さんから、

void
foo()
{
    printf("hello, before main\n");
}
namespace { struct foo_caller { foo_caller() { foo(); } } caller; }

の部分を別ファイルにして ar で静的ライブラリを作った場合は foo() が呼び出されなくなる、とご指摘を受けました。試してみると確かにその通りでした。これは、C++ のコンストラクタを用いた方法に限った話ではなく、 __attribute__((constructor)) を使っても同様の問題が起きます。

% g++ -c foo.cpp
% g++ -c main.cpp
% ar r libfoo.a foo.o
% g++ main.o libfoo.a
% ./a.out
hello, world

libfoo.a を作らずに foo.o をリンクした場合と、 libfoo.so を作ってリンクした場合は問題ありませんでした。

# foo.o をリンク
% g++ main.o foo.o
% ./a.out
hello, before main
hello, world

# libfoo.so を作ってリンク
% g++ -fPIC -shared -o libfoo.so foo.o
% g++ main.o ./libfoo.so
% ./a.out
hello, before main
hello, world

libfoo.a の場合でも、-Wl,--whole-archive libfoo.a -Wl,--no-whole-archive とオプションを渡して、強制的に libfoo.a に含まれるすべてのオブジェクトファイルをリンクするようにすれば、 foo() が呼ばれるようになります。

% g++ main.o -Wl,--whole-archive libfoo.a -Wl,--no-whole-archive
% ./a.out
hello, before main
hello, world

ライブラリ内の静的オブジェクトに関するこの辺りの挙動は、使っている OS の種類や GCC, GLIBC, Binutils のバージョンなどによって変わってくると思います。移植性を重視する場合は注意が必要です。なお、手元の環境は Debian GNU/Linux sarge の GCC 3.3.5 + GLIBC 2.3.2 + Binutils 2.15です。

まとめ

通常、main() の前の暗黙的な関数呼び出しが必要になることはないと思いますが、 LD_PRELOAD で変なハックをするといった場面では役に立つかもしれません。先日紹介した libSegFault.so では __attribute__((constructor)) を使ってシグナルハンドラをセットしています。

ところで、C のプログラムで lookup() の前に init_dictionary() を呼ぶ必要があるといった場面では、次のように書く方法もあります。この場合、 lookup() が最初に呼ばれたときに初めて init_dictionary() が呼ばれるため、 lookup() が一切呼ばれなかったときは init_dictionary() の呼び出しを節約できます。

static Dictionary *dictionary;

char *
lookup(const char *pattern) {
  if (dictionary == NULL) {
    init_dictionary();
  }
  ...
}

lookup() が必ず使われるとは限らず、かつ init_dictionary() の処理が重い場合は、init_dictionary() を main() の前に暗黙的に呼び出すよりも、この方法のほうが適しているかもしれません。ただし、この場合、lookup() の呼び出しはスレッドセーフではなくなります。 このような処理 (シングルトンの生成) を効率的にスレッドセーフにするのは 非常に奥が深いトピックのようです。

おまけ

C++ のコンストラクタを使う方法を応用すると、 main() が呼ばれる前に main() を呼ぶという変なことができます。だからなんだという感じがしますが…。

#include <stdio.h>

int main();
namespace { struct main_caller { main_caller() { main(); } } caller; }

int main() {
    printf("hello, world\n");
    return 0;
}
% g++ main-before-main.cpp
% ./a.out
hello, world
hello, world

同様に静的オブジェクトのデストラクタを応用すると、 main() が呼ばれた後で再び main() を呼ぶとことができます。さらに使い道がなさそうですが。

#include <stdio.h>

int main();
namespace { struct main_caller { ~main_caller() { main(); } } caller; }

int main() {
    printf("hello, world\n");
    return 0;
}