『実行ファイルサイズを減らす方法』を読んで作成される実行ファイルのサイズの違いに驚きましたが、そのまま使用していたのではC++の恩恵があまり得られませんでした。特にグローバルに宣言した変数のコンストラクタやデストラクタが走らないのは結構辛い。
もともとのLIBCではどのようにしているのかを調べて見ると、どうやら".CRT"というセクションには初期化するための関数へのポインタが入るようです。またグローバルに宣言された変数のデストラクタは atexit
という標準関数によって終了時に実行される関数に登録されます。この二つをサポートしてやることによりC++を使用していてもモジュールサイズをかなり小さくすることができます。
- typedef void (__cdecl* vfuncv)(void);
- // VCではこれらのセグメントに初期化処理を行う関数へのポインタを格納する
- extern "C" {
- #pragma data_seg(".CRT$XIA")
- static vfuncv __xi_a[] = {NULL};
- #pragma data_seg(".CRT$XIZ")
- static vfuncv __xi_z[] = {NULL};
- #pragma data_seg(".CRT$XCA")
- static vfuncv __xc_a[] = {NULL};
- #pragma data_seg(".CRT$XCZ")
- static vfuncv __xc_z[] = {NULL};
- #pragma data_seg()
- }
- // VCで自動生成された初期化処理を行う関数内では終了処理をatexitを呼んで登録している
- #define EXITFUNCS_BOUNDARY 8
- static vfuncv* __exit_a = NULL;
- static vfuncv* __exit_z = NULL;
- static vfuncv* __exit_zz = NULL;
- static void _callfunclist(vfuncv* pBegin, vfuncv* pEnd)
- {
- while (pBegin < pEnd){
- if (*pBegin != NULL)
- (**pBegin)();
- pBegin++;
- }
- }
- extern "C" int atexit(vfuncv pf)
- {
- if (pf == NULL)
- return 0;
- if (__exit_z == __exit_zz){
- UINT nSize = __exit_zz - __exit_a;
- vfuncv* new_funcs = new vfuncv[nSize + EXITFUNCS_BOUNDARY];
- if (new_funcs == NULL)
- return -1;
- vfuncv* pfDst = new_funcs;
- if (__exit_a != NULL){
- vfuncv* pfSrc = __exit_a;
- UINT nCount = nSize;
- while (nCount--)
- *pfDst++ = *pfSrc++;
- delete[] __exit_a;
- }
- UINT nCount = EXITFUNCS_BOUNDARY;
- while (nCount--)
- *pfDst++ = NULL;
- __exit_a = new_funcs;
- __exit_z = __exit_a + nSize;
- __exit_zz = __exit_z + EXITFUNCS_BOUNDARY;
- }
- *__exit_z++ = pf;
- return 0;
- }
- #pragma comment(linker, "/entry:WinStartup")
- extern "C" void WINAPI WinStartup()
- {
- _callfunclist(__xi_a, __xi_z);
- _callfunclist(__xc_a, __xc_z);
- // アプリケーションの情報を取得する
- // HINSTANCE hInst = モジュールのインスタンス
- // HINSTANCE hPrevInst = Win32ではNULLをいれる
- // LPCTSTR lpszCmdLine = アプリケーションに渡された引数
- // int nCmdShow = … // ウィンドウの初期状態
- int nResult = WinMain(hInst, hPrevInst, lpszCmdLine, nCmdShow);
- _callfunclist(__exit_a, __exit_z);
- delete[] __exit_a;
- ::ExitProcess(nResult);
- }
ただし、これだけではC++で得られる恩恵の一つ、例外処理が書けません。結局どう実装すればいいのかが判らずじまいなので、今のところプロジェクトの設定で例外を使用しないようにしています。まぁ、例外を使用したければサイズのことはここまで偏執的に気にしない方がいいでしょう。