C++処理系の特性に関する情報 SHINOHARA Takayuki 1997 / 1998 このテキストでは、以下のトピックについて記述しています。 目印の星マーク(☆)を検索して各トピックにジャンプすることができます。 ☆1 処理系依存動作 ☆2 オプション紹介 ☆3 Borland C++ version 5.0xJ のバグ ☆4 WATCOM C/C++ 10.5J の注意事項 ☆5 各種ターゲットのコンパイル方法 ☆6 コンパイラと環境で条件コンパイルする方法 ☆7 ライブラリ作成時のアラインメント保証について また、各処理系に対して以下の様な略称を用います。 略称 コンパイラ名称 コマンド BC5 Borland C++ 5.0xJ bcc/bcc32 BCB1 Borland C++ Builder bcc32 BCB3 Borland C++ Builder 3.0 bcc32 WC105 WATCOM C/C++ 10.5J wcl/wcl386 WC110 WATCOM C/C++ 11.0J wcl/wcl386 VC4 Microsoft Visual C++ 4.0 cl ☆1 処理系依存動作 ●コンパイラ識別用の定義済みマクロとその値 BC5: __BORLANDC__ 0x500 BCB1: __BORLANDC__ 0x520 BCB3: __BORLANDC__ 0x530 WC105: __WATCOMC__ 1050 WC110: __WATCOMC__ 1100 // __WATCOMC__の値はバージョン番号の100倍 VC4: _MSC_VER 1010 // VCの場合、コンパイラのversionが10.10.xxxxなので ●char型の扱い BC5: デフォルトで符号付き整数(-128 - 127)。 -K オプションで符号なし整数にする。 WC105: デフォルトで符号なし整数(0 - 255)。 -j オプションで符号付き整数にする。 ●new演算子でメモリが足りなかったときの動作 BC5: xalloc例外((独自)で宣言)を送出する。 受け取られなかった場合、abnormal program termination.と 標準出力に表示してプログラムが停止する。 WC105: NULL値を返す。 ●インラインアセンブラの書き方 BC5: asm構文によって展開場所に直接書く。 WC105: #pragma aux によってインライン関数として書く。 WC110: #pragma aux または _asm{}構文。(asm{}ではない) ●DOS, 割り込み関係のインクルードファイル BC5: WC105: , (独自) ●割り込み処理関数の引数(C++) BC5: ... WC105: void ●ファイルパス文字列処理関数(非ANSI)のインクルードファイル BC5: (独自) WC105: (独自) WC110: (独自), ●スタックオーバーフローの実行時チェック BC5: デフォルトではなし。 -N オプションで有効にする。 WC105: あり。 -s オプションで無効にする。 ●スタックサイズの設定方法 BC5: mainを含むプログラムの先頭で以下のコードを実行。(nは16進バイト) extern unsigned _stklen = 0xnnnn; WC105: -kn または-"op st n"オプションで設定。(nは10進バイト) ●コンパイラが生成したアセンブラコードをみる方法 BC5: -S オプション WC105: OBJファイルに対して WDISASM コマンドを使う。 WC110: OBJファイルに対して WDIS コマンドを使う。 ●コメントや文字列定数内の日本語(マルチバイト文字)対応 BC5: デフォルトでシフトJISに対応。 WC105: デフォルトでは対応しない。 -zk オプションで対応する。 ●リンカの大文字小文字区別 BC5: デフォルトで区別する。 -l-c オプションまたは -c- リンカオプションで抑制。 WC105: デフォルトで区別しない。 -x または-"op c" オプションで区別する。 ☆2 オプション紹介 ●Pentium速度最適化オプション BC5: -5-O2 -OI-OM-a4 -5はbcc32のみ, -a4はアライメント指定なので要注意 WC105: -5-fp5-oneatx -zp4 -zp4はアライメント指定なので要注意 ●すべての最適化の禁止 BC5: -Od WC105: -od ●シンボル情報の埋め込み BC5: -v WC105: -d2 最適化を維持するときは-d1 ●アライメント指定 BC5: -an (n=1,2,4,8、4と8はbcc32のみ) WC105: -zpn (n=1,2,4,8) ☆3 Borland C++ version 5.0xJ のバグ ●memset のバグ 状況: ラージメモリモデルを使う(-ml) 最適化オプション(-O2 -3)を使う memset(****, ****, (32768U以上65535U以下の値) ); を実行する。 症状: 確実にメモリを壊す。 詳細: 最適化のためストリング命令 rep stosw をインラインに 展開するが、事前に cx にセットする値が正しくない。 回避策:一度にセットする量を32768未満にする。 ●istream::operator>>(float&)のバグ 状況: ターゲットはWIN32(bcc32 -tW) istream::operator>>(flaot&); istream::operator>>(double&); istream::operator>>(long double&);等を使う。 症状: アプリケーションが不正な処理。 デバッガで追うと「浮動小数点例外」を発生している。 詳細: ウィンドウズアプリ用ライブラリのバグと思われる。 回避策:atof(const char*)を使ってどうにかする。 ☆4 WATCOM C/C++ の注意事項 ●日本語対応(WC105,WC110) Watcom C/C++ はデフォルトでは日本語に対応していません。 日本語をコメントや文字列定数内で問題なく使用するためには、 -zk オプションを指定する必要があります。 ●new演算子 new演算子はメモリ確保に失敗するとNULLを返します。Borland C などに慣れた人は特に、newが失敗しないものと勘違いしている ようです。newが失敗時に例外を発生するのはBorland Cの特殊事 情です。 ●ANSI C++ Watcom C/C++ は他のどの処理系よりも忠実にANSI準拠しています。 (これは経験上です)他の処理系ではOKだったコードがWatcom C/C++ で通らない場合、原因は標準的でないコードにある場合がほとんどです。 ☆5 各種ターゲットのコンパイル方法 ●MS-DOS 16-bit EXE形式 BC5: bcc [-mt,-ms,-mm,-mc,-ml,-mh] WC105, WC110: wcl -bt=dos -l=dos[-ms,-mm,-mc,-ml,-mh] ●MS-DOS DOS/4G EXE形式 WC105, WC110: wcl386 -bt=dos -l=dos4g ●Windows 95/NT コンソールモードEXE形式 BC5: bcc32 -tWC エントリーポイントはmain() WC105, WC110: wcl386 -bt=nt -l=nt エントリーポイントはmain()またはWinMain() ●Windows 95/NT GUIモードEXE形式 BC5: bcc32 -tW エントリーポイントはWinMain() WC105, WC110: wcl386 -bt=nt -l=nt_win エントリーポイントはmain()またはWinMain() ☆6 コンパイラと環境で条件コンパイルする方法 複数のコンパイラで同じソースをコンパイルしたいが、 どうしてもコンパイラ依存コードが必要になる時や、 異なるOS用のプログラムを同じソースに組み込みたい時などに、 条件コンパイルの手法が威力を発揮します。 以下に、分かっている範囲で条件判断のアルゴリズムを示します。 ●1 コンパイラ識別用マクロを使ってコンパイラを見分ける #ifdef __BORLANDC__ // Borland C++ 及び C++Builder 用コード #endif #ifdef __WATCOMC__ // Watcom C/C++ 用コード #endif #ifdef _MSC_VER // Visual C++ 用コード #endif ●2 ターゲットOSの見分け方はコンパイラ毎に異なる #ifdef __BORLANDC__ // (僕の知る限りでは)Borland C++は事前定義マクロで完全に識別できる # if defined(__MSDOS__) // MS-DOS (16-bit) # endif # if defined(_Windows) && defined(__CONSOLE__) // Windows コンソールモード # endif # if defined(_Windows) && !defined(__CONSOLE__) // Windows GUIモード # endif #endif #ifdef __WATCOMC__ // Watcom C/C++はリンク時にターゲットを選べるので、 // コンパイル時にターゲットOSを知ることが出来ない。 // しかも困ったのは-btオプションで、デフォルトの事前定義マクロ // (__DOS__,MSDOS,__OS2__,__NT__等)をすべて取り消し、 // 代わりに指定のマクロを定義する(例えば-bt=fooで__FOO__が定義 // される)が、コンパイルにのみ影響しリンクには強制力がないので、 // コンパイルを十分混乱させることができる。 // 一応、前記「各種ターゲットのコンパイル方法」の節で示した方法 // に忠実に従ってコンパイルした場合は、以下のように識別できる。 # if defined(__DOS__) && !defined(__FLAT__) // MS-DOS (16 bit) # endif # if defined(__DOS__) && defined(__FLAT__) // MS-DOS (DOS/4G) // または、その他のDOSエクステンダ # endif # if defined(__NT__) // Windows 95/NT // コンソールモードとグラフックモードを // コンパイル時に区別する方法は、 // -dオプションを使う以外考えられない。 // 実行時なら、GetStdHandle()APIでコンソールの有無を // 調べる手があるかもしれない。 # endif ☆7 ライブラリ作成時のアラインメント保証について 同じクラス定義を参照する異なる翻訳単位間でアラインメント指定が異なると、 予期しない実行時の不具合の原因になります。この問題はコンパイル済みのクラ スライブラリとそのライブラリユーザが作るアプリケーションプログラムの間で 起こりがちな問題です。 ユーザにアラインメント指定に関する注意を要求することは現実的ではないので、 処理系が提供するプラグマ機能を使って、この問題が起きないようにすることが 出来ます。 ●一般的な方法 具体的にはアラインメントを保証したいクラスのクラス定義部を次の ようなプラグマ指定で囲います。 #pragma pack(push,4) // ... #pragma pack(pop) この場合ダブルワード(4バイト)アラインメントを指定したことにな ります。プラグマ“pack(pop)”はアラインメント指定を元に戻します。 このpackプラグマによる指定が有効な処理系は以下のとおりです。 BC5以降, BCB1以降, WC105以降, VC5以降(あるいはそれ以前も) ●テンプレートクラスの場合 通常のクラスの場合は上記の方法で解決しますが、テンプレートクラ スの場合は事情が異なります。 問題はいつメンバ配置の評価が行われるかということです。そのタイ ミングによってはアラインメントが異なってしまうからです。 テンプレートクラスのメンバ配置の評価のタイミングは処理系によっ て異なるようです。 タイミングの候補としては次のものが考えられます。 A) テンプレートクラス自身の定義 B) 初めての、typedef X xxxx; C) 初めての、sizeof( X ) D) 初めての、X からの派生クラス定義 E) 初めての、X 型変数の定義 F) 初めての、X 型変数のextern宣言 詳しいことは分かりませんが、個人的な実験によって分かったことは、 以下のとおりです。 ・BC5では、C,D,E,F の内もっとも早いものが生じたときにアラインメン トが評価されるらしい。 ・WC110(WC105は未確認)では、B,C,D,E,F の内もっとも早いものが生じ たときにアラインメントが評価されるらしい。 なおアラインメント評価は、テンプレートパラメータが異なる具体化 ごとに別々に、それぞれのタイミングで行われることにも注意が必要 です。 ●テンプレートクラスの場合の対処法 上記のようにテンプレートクラスのアラインメント評価のタイミング は処理系によって微妙に異なるものの、おおよそ最初の具体化の時と 考えられます。 テンプレートクラスの具体化をメンバに持つクラスを含むライブラリ を作成する場合、その具体化が一意なアラインメント指定を持つこと を保証しなければなりません。 テンプレートクラスXの定義と、そのある具体化Xをメンバに持つ クラスYの定義が、1つのヘッダファイルに書かれている場合は問題あ りません。クラスYの定義部分は、テンプレートクラスXの具体化X の要求が初めてであることを保証できるからです。クラスYの定義部 分を前述のプラグマ指定で囲むことでアラインメントは保証されます。 テンプレートクラスXの定義とクラスYの定義が複数のヘッダファイル に分かれている場合は工夫が必要です。ユーザがテンプレートクラス Xの定義をインクルードしてその具体化Xを先に要求してしまうと、 その後にインクルードされたクラスYの定義部分では初めての具体化 ではないためにユーザのアラインメントに依存することになります。 この場合の僕が知りうる唯一の対処法は具体化Xをテンプレートク ラスXの定義の直後に何らかの形で記述してしまうことです。