mainとスレッドの前後処理(1)mainとスレッドの前後処理(1)この記事は連続記事の先頭です。次の記事はmainとスレッドの前後処理(2)です。この記事は早くも古いものとなりました。詳細はMinGW+pthread2010.03?を参照。mainとスレッドの前後処理(1)C/C++でmainの開始前・終了後に処理をしたいことがある。スレッドの開始前・終了後にも処理をしたいことがある。C++でこれらを行うためのメモ。Windows環境がターゲットだが、知識としてGCC全般(というかELF)に関わる部分も扱う。調査の結果、話が広がりすぎて上手くまとめられる自信がないのだけれど。日本語圏ではこれに関する記事が少ないようなので、メモレベルでも頑張って書いてみる。この記事からのリンク先は英語のサイトが殆どなので注意。Win32(以下pthread-w32)という有名なライブラリがある。このライブラリはバージョン2.8.0現在、staticリンクで使用するためには、使用する側のソースコードに細工が要る。詳細はMinGW+ffmpeg(5)pthreadやMinGW+ffmpeg(A)pthreadは複雑を見てもらうとして、プロセス・スレッドの開始時・終了時にそれぞれ、特定の関数を呼ぶ必要がある。POSIXのpthreadだけを想定して書かれているプログラムは、そのままpthread-w32をリンクするだけでは実行時にクラッシュする。このような仕様ではPOSIX互換とは言い難く、また、他人様の書いたコードを単にビルドして使用したい人間にとっては、毎回コード修正が必要というのは好ましい状況ではない。x264で対応状況がまちまちなことに困っていた。logという記事を見つけた。なんと、この制限を上手く取り払っているらしい。これがどう実現されているのか、という興味から今回は調査を始めた。コードを見ればある程度その仕組みは想像が着くのだが、ここでは理論的に追って行く。結論から言ってしまうと問題であるのはスレッドの終了時なのだが、一応全て追って行こう。猫科研究所では、pthread-w32でこの制限を撤廃するのは難しいだろうと予測していた。DLLではDllMain()関数でプロセス・スレッドの開始・終了を検知できるのは有名な話だが、スタティックリンクされるライブラリ側でそれが可能であるとは思えなかった。言われてみればその通りなのだが、プロセスに関しては標準C++の仕様の範囲内でも、この仕組は必要なのであった。以下のコードを見て欲しい。TDM-2)は以下の通り。ctordtor.execonstructor!main!destructor!そう、グローバルまたはスタティックなインスタンスのために、コンストラクタ・デストラクタはmainの外で呼ばれる。これはstaticリンクされるライブラリでも例外ではない。DLLではDllMainの前にDllMainCRTStartupが実行されるのは知っている。標準C/C++ではこれらの実装方法を規定しているわけではないが、そういう仕組自体は処理系に必ず必要ということだ。そこに割り込む方法があるかもしれないし、最悪、クラス化してC++のグローバルorスタティック変数にしてしまえばなんとかなる。プログラムが途中でexitしても当然デストラクタは実行されるし、インスタンスが1度も使用されずともコンストラクタ・デストラクタは実行される。上記の例で以下のようにしても"constructor!"と"destructor!"は表示される。0;}プロセスの開始・終了時処理の実装さて、言語レベルから下り、実装レベルの仕組みを見ると、Windowsではこれらを実行ファイル(PE)の特定のセクションを使用して実現している。セクションと言えば.textや.rdataなどが有名だが、普通のプログラミングをしている限り、それほど気に掛けることはない。組み込み系プログラミングやセキュリティ関連の若干深い部分では重要だが、一般的にはコンパイラ・リンカが自動的に適切に扱ってくれるからだ。セクション自体の説明は長くなるので省くが、誤解を恐れずに言えば名前のついたメモリ領域だと思えばいい。そのメモリ領域の名前によって、特殊な処理が行われるようになっている。ここで期待する処理とは、特定のセクション内のデータを関数へのポインタとみなし、決まったタイミングでそれらの関数を実行してくれることだ。この機能自体はOS(というか実行ファイルの形式)固有のものなので、コンパイラ・リンカによってその記述・指定方法は異なっても、最終的にはこのセクションの機能に到達する。この記事ではこれを初期化セクション(終了セクション)と呼ぶ事にする。初期化・終了セクションに関する基本的なルールは以下の通り。セクション名は".CRT$X??"(「?」には任意のアルファベット)にする。".CRT$XAA"〜".CRT$XZZ"あたりの名前が使用される。セクション名には大文字・小文字の区別がない。正確には"$"の前後で".CRT"をセクション名、"X??"をグループ名と言うらしい。アルファベットの若い順にソートされて格納される。最後の文字がAとZのセクションはマーカー(目印)として使用されるので、関数は登録せず0で埋める。".CRT"に属するセクションは最終的に".data"に統合される。CodeGuru.comによると、2文字目によって以下のように分類されている。".CRT$XI?"はCの初期化。".CRT$XC?"はC++の初期化。".CRT$XP?"は終了前処理(C++用?)。".CRT$XT?"は終了時処理。C++では以下の#pragmaで簡易的に定義できる。init_seg(compiler)コンパイラとCランタイムライブラリ用の初期化セクション。init_seg(lib)サードパーティ製のライブラリ用の初期化セクション。init_seg(user)ユーザ用の初期化セクション。init_seg("mysegment")のように任意のセクション名を指定する事もできる。詳しくはMSDNのinit_segを参照。これらの情報を総合すると、2文字目は言語処理系によって、3文字目は処理の深度によってルール付けされている。init_seg(user)を使って欲しいようだ。init_seg(lib)でもいいかもしれない。セクションの定義にはこの他、#pragmaディレクティブのsectionやdata_segを使う方法もある。((section("セクション名")))で特定のセクションへの配置ができる。GCCやLinuxの世界でメジャーなELFフォーマットもWindowsのCOFF・PEと同じような決まり(仕組み)があり、セクション名は.ctors(constructorsの意味)と.dtors(destructorsの意味)になる。C++の場合と同じようなものだ。こちらの方が単純で分かりやすい気もする。あとはスレッドの開始・終了だが、こちらは厄介だ。この記事は連続記事の先頭です。次の記事はmainとスレッドの前後処理(2)です。Perl5.010001。