|
解析上の注意点ヘッダファイルの解析通常は Imagix 4D アナライザへ渡すファイルのリストに、ヘッダファイルを記述する必要はありません。.c ファイルや .cpp ファイル、そしてインクルード・ディレクトリを指定することにより、#include 文で直接的あるいは間接的にインクルードされると、ヘッダファイルは自動的に解析されます。しかし、万が一 C または C++ のソースファイルによって他動的にインクルードされない場合でも、プロジェクトにヘッダファイルを追加するために、ヘッダファイルをリスト化しているのです。
システム / アプリケーション・インクルード・ディレクトリImagix 4D アナライザではコンパイラと同様に、インクルード・ディレクトリのリストを指定します。アナライザが #include プリプロセッサ・ディレクティブを認識すると、#include 文で指定された名前に一致するファイルが見つかるまで、指定された順序でインクルード・ディレクトリを検索します。デフォルトで Imagix 4D アナライザは、以下のような2つの include 文を個別に識別し、1点だけ異なる動作をします。
#include "fileA.h" #include <fileA.h>ファイル名がダブルクォーテーション(")で囲われた最初の文では、アナライザはまずカレントディレクトリで fileA.h を検索します。これ以外の点においては、アナライザはどちらの文でも同じ動作をします。つまり、指定されたインクルード・ディレクトリの検索を続けるのです。この検索は fileA.h が見つかるまで継続されます。 ある特定のインクルード・ディレクトリをシステム・インクルード・ディレクトリとして指定することが可能です。これは、imagix-csrc を呼び出す際に -I ディレクトリ名 ではなく -S ディレクトリ名 とオプションを指定することで実現できます。 -I ではなく -S オプションを使用することで考えられる影響が2つあり、両方ともソースアナライザのオプションに関係します。1つは -nosys オプションへの影響です。-nosys が設定されると、システム・インクルード・ディレクトリとして指定されたディレクトリにあるヘッダファイルは構文解析されますが、データファイルは生成されず、その内容を確認することができません。したがって -S オプションは、一般的にあまり関心を向けない stdio.h のようなヘッダファイルを格納している、インクルード・ディレクトリを指定する際に使用することを推奨します。これにより、不要なデータがプロジェクトのデータベースに蓄積することを、回避するのです。 -S と -I オプションにおける2つめの相違点は、-sysincfirst オプションを適用した場合に生じます。-sysincfirst オプションによって -I ディレクトリより前に、すべての -S ディレクトリで < > の記述により指定されたヘッダファイルの検索を実行します。慣例的な記述方法としては、
#include "appfile.h"と、アプリケーションのヘッダファイルを指定し、また
#include <sysfile.h>と、記述することでシステムのヘッダファイルをインクルードする場合に、 -sysincfirst オプションを使用することができます。またアプリケーションのヘッダファイルと、システムのヘッダファイルのファイル名が重複する場合に、これを解決する上で有用なことがあります。
パス名Windows ではパス名にスペースを使用することができ、また大文字と小文字を区別しません。これは Imagix 4D アナライザを実行する上で、大きな意味合いを持ちます。以下に、この例を示します。
(1) imagix-csrc -IC:\Program Files\Include -ox File.c (2) imagix-csrc "-IC:\Program Files\Include" -ox File.c (3) imagix-csrc "-IC:\PROGRAM FILES\INCLUDE" -ox File.cインクルード・ディレクトリである C:\Program Files\Include にはスペースが入力されており、このようにスペースを含む引数はダブルクォーテーションで囲う必要があります。 このため(1)では解析エラーが発生し、一方で(2)は正常に解析が実行されます。(2)および(3)については、Windows が大文字と小文字を区別しない仕様のため、同義となります。
関数ポインタC および C++ では関数を呼び出すため、物理メモリに割り当てる関数のエントリポイント対する、関数ポインタの使用をサポートしています。Imagix 4D ソースコード解析において、標準的な構文解析を超える領域の1つに、アナライザが取得する関数ポインタの静的側面に関する情報があります。関数名を含むデータとともに設定される変数はすべて、関数ポインタとしてマークされます。すべての関数はその静的初期化子において、この関数ポインタから呼び出される(可能性がある)ものとして、記録されます。ある関数が、関数ポインタを使用して呼び出しを実行する場合は、関数ポインタの変数を呼び出すものとして表示されます。この結果、該当の関数からその関数ポインタの変数へ、さらにこの変数に代入される可能性のある関数すべてに至るまでの呼び出しの関係をたどることで、潜在的なコールツリーを確認することが可能です。またアナライザは、ある関数ポインタから別の関数ポインタに対する代入も追跡し、関数ポインタもしくは仮引数として渡された関数を認識します。 この方法は、静的に初期化された関数ポインタの変数、関数ポインタの配列、または関数ポインタのメンバを持つ構造体 / 共用体 / クラスに対して有効に機能します。先にも述べましたが、アナライザは代入も追跡します。ところが変数が自動的に更新されたり(例: 別の関数名が文に代入されるなど)、関数ポインタの変数に到達するまでに関与するポインタがあったり、あるいは関数名が引数を介して渡される場合、グラフは呼び出される可能性のある関数と、関数ポインタのすべてを汎用化して表示します。また、特定の if 文によって除外されるであろう関数を削除するために、コントロール・ロジックの評価を試みることはありません。 処理方法について、以下に例を挙げます。
// function pointer relationships
int foo1();
int foo2();
int foo3(), foo4(), foo5();
typedef int (*fpT)();
fpT x1 = foo1; // 処理: 関数ポインタへ直接代入
fpT *x4 = &x1, x2 = x1, x3; // 処理: 関数ポインタへのポインタ
int *v;
fpT a1[] = {foo1, foo2, foo3}; // 処理: 関数ポインタの配列
fpT (*a2)[] = &a1; // 処理: 関数ポインタの配列へのポインタ
struct s1T {
fpT fp;
double w;
struct sinT {
int cnt;
fpT fp2;
} si[3];
} s1 = {foo3, 2.0}, // 処理: 関数ポインタを含む構造体
s2 = {foo2, 3.0, {{2, foo1}}}, // 処理: 関数ポインタの配列を含む構造体
*sp1 = &s1; // 処理: 関数ポインタを含む構造体へのポインタ
struct s1T s3 = {foo1, 0.4, {{3, foo4},{4,foo3}}}; // 処理
struct s1T tkentries[] =
{{foo1, 4.0, {{1, foo2}}}, {foo3, 5.0}}; // 処理
fpT goo1()
{
fpT r = &foo4;
if (v) return r; // 処理: 戻り値
else return foo5; // 処理: 戻り値
}
void goo2(fpT &fp)
{
fp = foo3; // 処理: 外部パラメータ
}
int bar1()
{
fpT locfp;
(*x1)(); // 処理 -> foo1,foo2
goo1()(); // 処理 -> foo4,foo5
*v = 2;
(s1.fp)(); // 処理 -> foo1,foo2,foo3
x3 = s1.fp; // 処理
*x4 = foo3; // 処理
(*a2)[1](); // 処理 -> foo1,foo2,foo3
goo2(locfp);
(*locfp)(); // 処理 -> foo3
}
int bar2(fpT p, fpT q)
{
if (v) p = foo3; // 処理
if (*v) p = x1; // 処理
(*p)(); // 処理 -> foo1,foo2,foo3
x1 = foo2; // 処理
x2 = sp1->fp; // 処理
s1.fp = foo1; // 処理
x3 = goo1(); // 処理
q(); // 処理 -> foo2
}
int bar3()
{
(*a1[*v])(); // 処理 -> foo1,foo2,foo3
(*sp1->fp)(); // 処理 -> foo1,foo2,foo3
bar2(foo1,foo2); // 処理
(*x2)(); // 処理 -> foo1,foo2,foo3
(*x3)(); // 処理 -> foo1,foo2,foo3,foo4,foo5
s2.si[0].fp2(); // 処理 -> foo1,foo2,foo3,foo4,foo5
s3.fp(); // 処理 -> foo1,foo2,foo3
(*s3.si[1].fp2)();// 処理 -> foo1,foo2,foo3,foo4,foo5
(**x4)(); // 処理 -> foo1,foo2,foo3
while (*x4) (*x4++)(); // 処理 -> foo1,foo2,foo3
(sp1++->fp)(); // 処理 -> foo1,foo2,foo3
(x2=foo1)(); // 処理 -> foo1,foo2,foo3
}
なお、関数ポインタによって呼び出される可能性がある関数を明示することで、Imagix 4D のデータベースに、より多くの情報を追加することが可能です。その方法の1つに、該当する情報を保存したデータファイルの作成があげられます(「データの追加 - vdb ファイル」のページを参照)。この他に、ソースコードを変更する方法もあります。関数ポインタによって呼び出される関数が判明している場合は、以下のようにソースコードを追記することで、Imagix 4D はコールツリーを完成させることができます。
#ifdef IMAGIX_ONLY
FunctionPointerType fp = {func1, func2, ..., funcN};
#else
FunctionPointerType fp; // fp に対する通常の定義
#endif
また構造体の場合、関数はメンバに関連付けられることに注意してください。これにより同じ構造体の型を持つ、まったく異なる2つの構造体変数が同時にマージされ、同じ宣言の相互参照は取得されません(例: "FPTR x = foo, y = x;")。
潜在的な関数ポインタとしての void* の処理は、-voidfptr オプションを指定することで制御されます(「アナライザの構文とオプション」のページを参照)。また、関数ポインタのコピーに memcpy 関数が記述されている case は、../imagix/user/cc_lib ディレクトリに格納されている libc.inc を組み込むことで追跡することが可能です。使用方法については、libc.inc に記述されているコメントを確認してください。
|