2007年10月25日

Visual C++の標準ライブラリとcrtdbg.h

先日のcrtdbg.hを用いたメモリリークの自動検出の記事に関連して、開発中のソフトウェアに組み込んだ際に気づいた点がある。それはcrtdbg.hを#includeしなくても、関連の関数が使えてしまうことだ。それでは非常に不気味なので色々試行錯誤をしてみると、どうやらC++の標準ライブラリであるstringの#includeに問題があり、削除してみたところ、関数が使用できなくなった。そこで原因を探るべく、Visual C++のincludeディレクトリのヘッダーファイルを開いてざっと眺めてみたところ、どうやらstringから様々なヘッダーファイルに依存した結果、最終的にcrtdbg.hを#includeしてしまっているようだ。以下に依存関係を列挙しておく。

#include <string> ← #include <istream> ← #include <ostream>
← #include <ios> ← #include <xlocnum> ← #include <streambuf>
← #include <xiosbase> ← #include <xlocale> ← #include <xdebug>

最後のヘッダーファイルであるxdebugの22行目より

 #if defined(_DEBUG)
  //省略

  #include <crtdbg.h>

マクロ名_DEBUG、つまりVisual StudioのIDEでReleaseではなくDebugでビルドした場合に、問答無用でcrtdbg.hがロードされるわけである。何が問題なのかというと、crtdbg.hではグローバルスコープのnew演算子をオーバーロードしているので、仮に別途new演算子をオーバーロードしている場合にエラーとなってしまうのである。さらに他にもistreamをロードしている標準ライブラリとして、iostreamやiomanip、ios、sstreamなど多数のライブラリがロードを行っているようである。Visual C++で標準ライブラリを用いてなお且つnew演算子をオーバーロードする場合の解決策として、直感的にはxdebugの問題箇所にメスを入れれば良いが、根本的な問題はVisual C++の標準ライブラリの実装の問題なのでスマートではない。


2007年10月24日

メモリリークの自動検出と特定の方法

C/C++では他の言語ではあまりお目にかからない、メモリアロケーションを扱うことが出来る。便利な反面、使い方のお作法を間違えると利点以上の代償を払うことになる。メモリリークが発生する大きな要因としては、確保したメモリの開放のし忘れである。Javaなどとは違いガーベージコレクタは標準ではサポートされておらず、明示的な開放の記述が必要である。
Visual C++では、crtdbg.hというヘッダーにメモリリークを検出し、デバッグログに出力を行ってくれる独自の関数が定義されている。以下に使い方や応用例を示しておく。


まず、ヘッダーをロードしている記述のある場所を以下のように書き換えを行う。

#define _CRTDBG_MAP_ALLOC

//ここに任意の#includeを記述する
//例えばwindows.hやstdio.hなど

#ifdef _DEBUG
  #include <crtdbg.h>
  #define new  ::new(_NORMAL_BLOCK, __FILE__, __LINE__)
#endif

このような順番で記述を行う理由としては、まず下の方の#defineで既存のnewをオリジナルの記述へと置き換えを行っているが、これはcrtdbg.hのバグ対策用であり、new演算子を用いた場合に適切なファイル名がデバッグログに出力されない問題を修正するためのものであるが、たとえばこの記述を標準ライブラリより前に行ってしまうと、プリプロセス時に関係の無いnew演算子まで置き換えを行ってしまい、エラーを起こしてしまうからである。なお#defineで_CRTDBG_MAP_ALLOCの定義を一番先頭で行っているのには深い理由があるのだが、その解説は後日行う。

次にmain関数もしくはWinMain関数の先頭で以下の関数を呼び出す。

_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
これは、プログラム終了と同時にVisual Studioのデバッグログにメモリリークの有無を出力することを指定する記述である。

以上の記述により、malloc(int);やnew int;などを行ったままでプログラムを終了すると、デバッグログにメモリリークの詳細が出力される。

ちなみに、もしGUIやDirectXを用いたソフトウェアを開発している場合に、AllocConsoleなどで確保したコンソールにメモリリークのログを出力したい場合は、WinMain関数の一番最後の行に以下の記述を行うことにより可能となる。

HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);  //標準出力のハンドルを取得
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);  //ログの出力対象をファイルハンドルに指定
_CrtSetReportFile(_CRT_WARN,hStdout);  //ワーニングの出力対象を第二引数のストリームに指定
_CrtDumpMemoryLeaks();  //実行時点での開放されていないメモリの一覧を表示


参考:
メモリ リークの検出と特定
_CRTDBG_MAP_ALLOC マクロは、正常に機能しません。

2007年10月11日

東芝製扇風機F-LH25Xについて

20071011_1.jpg
メーカーHPへ

今年の夏の初めに購入した東芝製扇風機のF-LH25X。
同一価格帯の中ではデザインも良く、タイマーや風量調整、リズム風など一通りの機能が揃っているのが購入の決め手。

しかし購入当時から首振り機能をオフにするスイッチを引っ張ると、一定の周期で扇風機の首元から「カタカタ」、「カチッカチッ」と異音が発生する。その時は「そういう仕様」なんだと思い込んでいたが、就寝時にその音が結構気になり始めたので製品の型番で検索をしてみると、同一の不良の事例がありどうも初期不良の多い機種でもあるらしい。基本的に扇風機は購入してから数年と長く使うものなので、保障期間が有効なうちに、購入した家電量販店へ持ち込み修理をお願いしてもらうことに。ちなみに異音の原因として推測だが、首振り機能のスイッチ部分が結構緩く、それが原因で他の部品に接触している感じがした。もしくは部品の取り付け不良といったところだろう。

そして先日、家電量販店から電話があり、結局製品は新品と交換とのこと。単純に分解して該当部品の交換だけで修理できないということなのだろうか。交換品は問題であった首振り機能のスイッチ部分の緩さが解消されており、しっかり動作するようになっていた。

2007年10月06日

Visual Studioのプロジェクトのプロパティについて

私はVisual Studio 2005 Express Editionを使ってソフトウェアの開発を行っている。プロジェクトで必ず設定する項目として、[プロジェクトのプロパティ]→[構成プロパティ]→[全般]にある、プロジェクトの既定値の[文字セット]の部分をDebugとRelease共にデフォルトの[Unicode 文字セットを使用する]から[マルチ バイト文字セットを使用する]に変更の後、同構成プロパティの[C/C++]→[コード生成]にある、[ランタイム ライブラリ]の部分をDebugとRelease共にデフォルトの[マルチスレッド デバッグ DLL (/MDd)]から[マルチ スレッド デバッグ (/MTd)]に、[マルチ スレッド DLL (/MD)]から[マルチスレッド (/MT)]への変更を、手動で行っている。
新しいプロジェクトを始めるごとに手動で行うのは手間がかかり、設定のし忘れの防止のためにも出来ることならデフォルトの値を設定しておきたかったが、MicrosoftのリファレンスやWebで検索しても見つからなかったので、手動で設定ファイルを書き換えてデフォルトの設定を適用する方法をメモしておく。


1.Visual Studio 2005をインストールしたディレクトリ(Microsoft Visual Studio 8)から、\VC\VCWizards\AppWiz\Generic\Application\scripts\1041\へアクセスする

2.その中に入っているdefault.jsを念のためバックアップを取っておく

3.メモ帳などで開き以下の要領に従い文字セットの書き換えを行う
141行目(Debug用設定)にある
config.CharacterSet = charSetUNICODE;

config.CharacterSet = 希望する値;
に書き換える。希望する値は以下の通りである。

charSetNotSet; //設定なし
charSetUNICODE; //Unicode 文字セットを使用する
charSetMBCS; //マルチ バイト文字セットを使用する


同様に211行目(Release用設定)の書き換えを行う。

4.以下の要領に従いランタイム ライブラリの書き換えを行う
151行目(Debug用設定)にある
CLTool.RuntimeLibrary = rtMultiThreadedDebugDLL;

CLTool.RuntimeLibrary = 希望する値;
に書き換える。希望する値は以下の通りである。

rtMultiThreaded //Multi-threaded (/MT)
rtMultiThreadedDebug //Multi-threaded DLL (/MD)
rtMultiThreadedDebugDLL //Multi-threaded Debug DLL (/MDd)
rtMultiThreadedDLL //Multi-threaded Debug (/MTd)

同様に220行目(Release用設定)の書き換えを行う。

5.default.jsの保存を行い、実際に新規プロジェクトにて変更が適用されているか、エラーが出ないかを確認を行う

2007年10月05日

DirectX 9: 関数の返すHRESULT型について

DirectXのDirect3Dメソッドの戻り値にHRESULT型がある。実行した関数が何らかの原因でエラーとなった場合に、D3DERR列挙型でエラーの区分を戻り値として返す。ユーザーが定義する関数の戻り値としてHRESULT型を定義する場合などもD3DERR列挙型の定義に従って戻した方が自然であるので、以下によく用いる列挙名やマクロとそのメモをしておく。

#define FAILED(Status) ((HRESULT)(Status)<0)
#define SUCCEEDED(Status) ((HRESULT)(Status)>=0)

S_OK
→エラーは発生していない
E_FAIL
→Direcnt3D サブシステム内で原因不明のエラーが発生した
E_INVALIDARG
→無効なパラメータが関数に渡された
E_OUTOFMEMORY
→Direct3D が呼び出しを完了するための十分なメモリを割り当てることができなかった。

ところでS_OKと似た形態であるS_FALSEというものがあるが、MSDNで調べてみると、COMライブラリが既に初期化されている場合に返される値であり、成功を示している。なのでユーザー定義の関数の戻り値に勘違いでS_FALSEを返した場合に、呼び出し元でSUCCEEDEDマクロを使ってしまうと成功してしまうので注意。

Visual StudioのIntelliSenseについて

Visual Studioを使ってソフトウェア開発している人には欠かせないであろう、IntelliSense。クラス名の自動補完などを行ってくれるので、その都度クラスのヘッダファイルを見る必要が無く、引数の型も表示してくれる便利な機能。

ところでつい最近、何故かIntelliSenseが反応しなくなってしまった。その時に行った修復方法をメモしておく。


1.一旦、開いてるソリューションを保存しVisual Studioを終了する
2.ソリューションのデータのあるディレクトリに移動する
3.プロジェクト名.ncbというファイルを削除又はリネームする
4.ソリューションを開き、IntelliSenseを使う


ソリューション名.ncbというファイルは、クラス名やメンバ変数、メンバ関数を管理するデータベースファイルである。それを削除することにより、破損していたncbファイルの再構築が行われるので再び利用可能となる。
なお、プログラムに問題があってIntelliSenseが動作しない場合も多いので事前に確認をしておくこと。

2007年10月04日

DirectX 9: レンダリングパイプラインの構造

レンダリングパイプラインを構成する三要素とその役割

●頂点シェーダ
頂点バッファを入力とし、座標変換やライティングを行う。
座標変換はカメラ座標などを考慮し、最終的に出力する座標系への変換を行う。
ライティングはポリゴンに光を当てたときに、ライト、マテリアル、法線ベクトルの情報を元に、どのように色が変化するか、どのように反射をするかの計算を行う。処理後はディフューズとスペキュラーの二つの成分に分割される。ディフューズは色成分、スペキュラーは光沢成分である。

●ラスタライザ
頂点シェーダで処理された頂点データを入力とし、各頂点データから三角形を構成するデータを作成する。これをトライアングルセットアップと言う。そしてDDA(Digital Differential Analyzer)により、各頂点データを用いて三角形の内部をピクセルで塗りつぶしていく。この際にピクセル間の色を補完する仕事も行う。

●ピクセルシェーダ
テクスチャと、ラスタライザで処理されたデータをもとに、テクスチャマッピングや、アルファブレンディング、フォグなどの処理を行う。そしてレンダリングターゲット(サーフェイス)へと、出力を行う。