' の参照を確認してください
インラインの制御の改善
inline および __inline キーワードは、関数のインライン展開を試みるように指示するヒントであり、オプティマイザは最適化スイッチ (サイズと速度のどちらを最適化するか) とその他のヒューリスティックスに基づいて、インライン展開を行うかどうかを決定します。
オプティマイザの決定をオーバーライドして、つねにインライン展開を行うように指示する __forceinline キーワードが追加されました。ただし、コンパイラはコマンド ライン スイッチやその他の理由から、インライン展開を行わないことがあります。たとえば、オプティマイザは以下のケースではインライン展開を行いません。
- デバッグ ビルドのデフォルトである /Ob0 が指定されている場合。
- 関数呼び出しの中でのSEH/EHのミスマッチ (SEH が EH を、または EH が SEH を呼び出
している)。この場合、呼び出された側をインライン展開することはできません。
- 可変長引数リストを取る関数。
- 関数の再帰。この動作は、#pragma(inline_recursion, on) を指定することで、再帰のケースでもインライン展開を行うように変更することができます (ただし下記の制約があります)。
- インラインの深さが上限を超えた場合。これは再帰のケースに適用されます。この値のデフォルト値は 8 で、#pragma(inline_depth, ) (n は 256 未満の数値) と #pragmainline_recursion(on | off) で制御することができます。
- コピー構築されたオブジェクトを値で受け取る関数を、-GX/EHs/EHa でコンパイルした場合。
- アンワインド可能オブジェクトを返す関数を、-GX/EHs/EHa でコンパイルした場合。
- インライン アセンブラを使用する関数を、-Og/Ox/O1/O2 でコンパイルしなかった場合。
__forceinline が指定されたのに、関数のインライン展開を行えなかった場合、コンパイラはレベル 1 警告 (4714) を生成します。
このキーワードを使うときには、コードの動作と、コード サイズとコードの速度の間のトレードオフを理解し、結果を測定することが重要です。コードが大型化し、実行速度が低下する可能性があります。
注: __forceinline が正常に機能しているかどうかは、必ずしもマップ ファイルからは判断できません。これは、関数のアドレスを取り出したり、関数をエクスポートしたりすると、関数のインスタンスが生成されるからです(この場合でも、関数が名前で呼び出されたときにはインライン展開が行われています)。
関数のアドレスを取る式が存在すると、OBJ の中に関数のインスタンスが強制的に作られます。関数が関数ポインタを通して呼び出されると、OBJ の中の関数のインスタンスが呼び出されます。ただし、関数が名前で呼び出されたときには、(他にも制約がありますが) 関数のインライン展開が行われます。
これは意図的な設計です。インライン展開はコンパイル時の操作であり、関数ポインタを通しての関数呼び出しは実行時の操作です。きわめて単純な例(たとえば、コンパイラが「ポインタを通しての関数呼び出しのアドレス」を最適化して関数に渡せる場合) を除き、関数ポインタを通しての関数呼び出しはインライン展開されませんし、その試みも行われません。
__assume キーワード
__assume キーワードは、引数として渡された式が、式が (他の変数との干渉や、何らかの変数への代入などによって) kill されるまで真であるということをオプティマイザに知らせるヒントです。デバッグ ビルドの際には、これを assert() と組み合わせて、__assume で使用している式がつねに真であることを確認することができます。
このキーワードのフォーマットを次に示します。
__assume(expr)
__assumeは一般にassertマクロの中で使用します。次に例を示します。
#ifdef DEBUG
# define ASSERT(e) (void) ((e) ? 0 : assert(__FILE__, __LINE__))
# define ASSERTNR(e) (void) ((e) ? 0 : assert(__FILE__, __LINE__))
#else // RELEASE build
# define ASSERT(e) (void) (((e) ? 0 : assert(__FILE__, __LINE__)), __assume(e))
# define ASSERTNR(e) (__assume(e))
#endif
#define UNREACHABLE 0
void foo(int *p)
{
// Optimizer can eliminate p==NULL checks which might follow
ASSERTNR(p!=NULL);
switch(bar()){
case 1:
blah(1);
break;
case 2:
blah(-1);
break;
default:
// default case unreachable, so the optimizer
// can assume bar() only returns 1 or 2, and
// eliminate range checks
ASSERTNR(UNREACHABLE);
}
}
ランタイムのエラー チェック
Visual C++ 6.0 コンパイラは、一般的なバグを検出するいくつかのランタイム チェックを埋め込むことができます。これらのチェックは、-GZ コンパイラ フラグによって有効となります。ランタイム チェックはデバッグ (-Od) ビルドでしか使用できません。
以下のチェックが追加されています。
ローカル変数の自動初期化
すべてのローカル変数が特定の値 (0xCC) に初期化されます。これにより、初期化されていない変数が使用されているケースをキャッチすることができます。
関数ポインタ コール スタックの正当性チェック
スタック ポインタ (ESP) をチェックして、関数ポインタを通じた呼び出しの前後で変化がないかどうかを確認します。これにより、関数が __stdcall、__fastcall、または__thiscalL の呼び出し規約を使っている場合に、関数プロトタイプのパラメータの数が間違っているようなケースをキャッチすることができます。ただし、__cdecL の場合は、呼び出し側がパラメータのプッシュとポップを同時に行うため、キャッチされません。
また、__stdcall 関数が __cdecl 関数ポインタを通して呼び出されているケースもキャッチします。
コール スタックの正当性チェック
関数の終わりにスタック ポインタ (ESP) をチェックし、スタック ポインタが変更されていないことを確認します。これにより、ESP がインライン アセンブラの中で破壊されているケースや、関数の呼び出し規約が誤って宣言されているケースをキャッチすることができます。次に例を示します。
void foo(int i); // misdefinition - should be void __stdcall foo(int
i);
void bar() {
...
foo(4);
...
// the ESP corruption caused by foo will be caught here, as bar exits
}
この例では、foo() のプロトタイプが間違っていることが検出されます。
operator delete の placement 形式
operator delete の placement 形式が新たにサポートされました。これにより、placement new が使用されている場合に、正しい delete 関数が呼び出されるようになります。
↑ 戻る
リンカの変更点
インポート ライブラリの形式の変更
インポート ライブラリの形式が、ライブラリ ファイルのサイズを小さくするように変更されました。
ほとんどのファイルが 50〜75% も小さくなっています。
遅延ロードインポート
リンカは、ダイナミック リンク ライブラリ (DLL) の遅延ロードを自動的に管理するためのコードを作成できるようになりました。これは、アプリケーションが DLL にリンクしているが、つねに使用するわけではないようなケースできわめて有効です。リンカ コマンドに次のスイッチを追加するだけで、遅延ロードが有効となります。
/DELAYLOAD:
マップ ファイルのエクスポートの表示
エクスポートのリストを得るには、リンカのコマンド ラインに次のスイッチを追加します。
/MAPINFO:EXPORTS
↑ 戻る
デバッガの変更点
エディット コンティニュー
エディット コンティニューにより、デバッガから出ずにコードを変更して実行を続けることができるため、生産性が大幅に改善されます。リビルドが必要な変更もありますが、ほとんどのケースではコードを変更してもそのままデバッグを続けることができます。
エディット コンティニューを有効にする方法
注: エディット コンティニューは、Visual C++ 6.0 で作成されたデバッグ プロジェクトでは、デフォルトで有効となっています。しかし、以前のバージョンの Visual C++ からプロジェクトをインポートした場合には、以下の操作を行う必要があります。
エディット コンティニューを有効にするには、次の設定を変更する必要があります。
- [ツール] メニューの [オプション] をクリックし、[デバッグ] タブの [デバッグ命令でエディット コンティニューを実行]チェック ボックスをオンにします。
- [プロジェクト] メニューの [設定] をクリックし、[C/C++] タブの [デバッグ情報]で[エディット コンティニュー用のプログラム データベース] を選択します。
- [プロジェクト] メニューの [設定] をクリックし、[リンク] タブの [カテゴリ]で[カスタマイズ] を選択し、[インクリメンタル リンクを行う] を選択します。
- さらに、プロジェクトを完全にリビルドする必要があります。
エディット コンティニューの使用
エディット コンティニューは、ほとんどの点でユーザーに対しては透過的に実行されます。編集した後にステップまたは実行を選択するだけで、エディット コンティニューが開始されます。コード編集がサポートされていない場合には、通常と同じようにリビルドを行うように求めるプロンプトが表示されます。
制約
エディット コンティニューがサポートしていない操作の例を示します。
- ヘッダー ファイルの編集
- C++ クラス定義の編集
- 関数プロトタイプの編集
- グローバル/静的コードの編集の大部分
バリアントとGUIDの表示のためのデコード
バリアントの表示
バリアントは自動的に正しい形式で表示されます。たとえば、整数は数値として、BSTR は文字列として表示されます。また、バリアントの型も表示されます。次に例を示します。
VARIANT vt;
vt.vt = VT_I2;
vt.iVal = 123
この例では、vt はローカル、ウォッチ、およびデータヒントのウィンドウに{123 VT_I2}と表示されます。VT_BYREF オブジェクトも、Microsoft Foundation Class(MFC) のCOleVariant と同様に、自動的に表示されます。
GUID とIIDの表示
IID、CLSID、およびREFIID を含むグローバル ユニーク識別子 (GUID) は、レジストリ内に名前が見つかれば名前によって、またそれに失敗した場合には通常の GUID の 16 進形式で表示されます。
スレッド情報ブロック
カレント スレッドのスレッド情報ブロック (TIB。TEB と間違って呼ばれることもあります) を表示する新しい擬似レジスタが追加されています。
普通のレジスタと同様に、@記号を前に付けることができます。TIBの内容に関する情報は、"Under the Hood"、Microsoft Systems Journal(MSJ), May 1996 にあります。
レジスタとして表示されるGetLastError()値
カレント スレッドの最後のエラー コードを表示する、ERRという名前の新しい擬似レジスタが追加されています。これは、GetLastError API を呼び出したときと同じ値を取得します。これを",hr"修飾子と組み合わせて使うと、非常に便利な出力が得られます。普通のレジスタと同様に、@記号を前に付けることができます。
MMX レジスタの表示
シンボル MM0〜MM7 を使って、MMX レジスタをウォッチ ウィンドウとクイックウォッチ ウィンドウに表示できるようになりました。MMX レジスタは64 ビット整数のレジスタで、MMX 命令をサポートするかどうかにかかわらず、すべてのx86マシン上で表示されます。MMX は IntelCorporation の登録商標です。
Vテーブルと関数ポインタ表示の改善
関数へのポインタとvテーブル エントリは、Visual C++ 5.0 のように単なる16進アドレスではなく、パラメータを含めて、可能な限りシンボルとして表示されます。
逆アセンブラ出力の改善
逆アセンブラの出力が、特に出力におけるシンボルの使用の面で改善されています。
アンデコレートされるシンボル
逆アセンブラとコールスタック ウィンドウは、DBG ファイルのシステム コールスタックを表示するときなどに、以前とは違ってC++名をアンデコレートするようになりました。たとえば、rpcrt4.DLL の中のある関数は、これまでは次のように意味不明の表示になっていました。
?MTAInvoke@@YGJPAUtagRPCOLEMESSAGE@@KPAUIRpcStubBuffer@@PAUIRpcChanne
lBuffer@@PAK@Z
現在では、正しい引数リストとともに、次のように表示されます
MTAInvoke
新しい書式指定記号
次の表は、Visual C++ 6.0 のウォッチ変数で使える新しい記号を示しています。
| ,hr |
32 ビット値 (HRESULT または Win32 エラー コード) を、よりわかりやすい形で表示します。たとえば S_OK や E_NOTIMPL など、または "File not found" などのシステム エラー メッセージなどです。残念ながら、autoexp.dat の中で自動的な規則を設定することはできません。HRESULT などの型に対してこれを行うためには、ウォッチ ウィンドウでシンボル名に ",hr" のポストフィックスを手動で追加する必要があります。
| ,st |
Unicode 文字列の設定に応じて、文字列値を ANSI または Unicode として表示します。これはデフォルトの autoexp.dat ファイルで、現在の設定に従って MFC のCStringを表示するために使用されています。
| ,mq |
既存の mb、mw、および md の書式指定記号と同様に、メモリをクワッドワードとして表示します。
| ,wm |
数値をウィンドウ メッセージ番号 (WM_定数) にデコードして表示します。たとえば、次のコード
WORD wMessage = WM_CLOSE
は、wMessage,wm を {WM_CLOSE} と表示します。
|
| ,wc |
数値をウィンドウ クラス フラグ (WC_定数) にデコードして表示します。
| | | |
[COFF とエクスポートのロード] オプション
この新しいオプションは追加のデバッギング情報をロードします。このオプションは [オプション] ダイアログ ボックスの [デバッグ] タブにあります。これはデバッグ対象のコードのロード時のパフォーマンスに影響を与える可能性があるので、デフォルトではオフになっています。
COFF シンボル
他のデバッガは COFF シンボルを使用しますが、Visual C++ は使用しません (代わりに CodeView フォーマットのデバッグ情報を使用します)。しかし、他に情報がないときには、COFF 情報を読めると便利なことがあります。たとえば、Microsoft Visual Basic 5.01 開発システムにはデバッグ情報を COFF フォーマットで提供する msvbvm50.dbg ファイルがありますが、このオプションをオンにしておけば、このファイルを読み込むことができます。.dbgファイルにどのような形式のデバッグ情報が含まれているかを調べるには、dumpbin -headers を使用し、DebugDirectory で COFF を探してください。
エクスポート
他にデバッギング情報がない場合(たとえば Microsoft Windows 95 オペレーティング システムファイルなど)、このオプションはロードされている個々の DLL のエクスポート テーブルをシンボルに変換します。特定の DLLで、このオプションがどのような種類のシンボルを追加するかを調べるには、dumpbin -exports を使用します。
モジュール リスト
アプリケーションがシステム DLL や他の人のコードの呼び出し中にクラッシュしたとき、クラッシュの発生時にどの DLL がアクティブになっていたかを知るにはどうすればいいでしょうか? VisualC++ 6.0 の新しいモジュール リストは、プログラムが使用したすべての DLL のアドレス、名前、パス、およびロード順序を表示します。このダイアログ ボックスを使って、アプリケーションがクラッシュしたときにどの DLL がアクティブになっていたかを追跡することができます。
結論
Visual C++ 6.0 では、エディタ、コンパイラ、リンカ、およびデバッガに大きな変更が加えられていますが、Visual C++ の以前のバージョンからのプロジェクトの移行はスムーズに行うことができます。しかし、Visual C++ 6.0 の新しい機能をフルに活用するためには、どのような新機能があるのか、既存のコードにどのような影響があるのか、そしてどのように使えばいいのかを理解する必要があります。このドキュメントでは、これらの情報を提供しようと試みましたが、実際に理解するための一番の近道は、Visual C++ 6.0 をインストールし、サンプルをビルドし、コードをインポートすることです。このドキュメントと MSDN ライブラリを手がかりに、Visual C++ 6.0 をフルに活用してください。
↑ 戻る