C++ に関する基本的なデバッグ・タスクの説明では、下記 の C++ プログラムを参照します。
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
下記のプログラムは、デバッグ・タスクを示すために各種のトピックで取り上げられます。
このプログラムは、その入力を文字バッファーから読み取る単純な 計算プログラムです。整数を読み取ると、スタックに 入れます。演算子 (+ - * /) の 1 つが読み取られると、最上位の 2 つのエレメントがスタックから取り出され、それらのエレメントに演算が行われ、結果がスタックに 入れられます。演算子 = は、スタックの一番上のエレメントの値をバッファーに 書き出します。
CALC.HPP
/*----- FILE CALC.HPP ------------------------------------------------*/
/* */
/* Header file for CALC.CPP PUSHPOP.CPP READTOKN.CPP */
/* a simple calculator */
/*--------------------------------------------------------------------*/
typedef enum toks {
T_INTEGER,
T_PLUS,
T_TIMES,
T_MINUS,
T_DIVIDE,
T_EQUALS,
T_STOP
} Token;
extern "C" Token read_token(char buf[]);
class IntLink {
private:
int i;
IntLink * next;
public:
IntLink();
~IntLink();
int get_i();
void set_i(int j);
IntLink * get_next();
void set_next(IntLink * d);
};
class IntStack {
private:
IntLink * top;
public:
IntStack();
~IntStack();
void push(int);
int pop();
};
CALC.CPP
/*----- FILE CALC.CPP ------------------------------------------------*/
/* */
/* A simple calculator that does operations on integers that */
/* are pushed and popped on a stack */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.hpp"
IntStack stack;
int main()
{
Token tok;
char word[100];
char buf_out[100];
int num, num2;
for(;;)
{
tok=read_token(word);
switch(tok)
{
case T_STOP:
break;
case T_INTEGER:
num = atoi(word);
stack.push(num); /* CALC1 statement */
break;
case T_PLUS:
stack.push(stack.pop()+stack.pop());
break;
case T_MINUS:
num = stack.pop();
stack.push(num-stack.pop());
break;
case T_TIMES:
stack.push(stack.pop()*stack.pop() );
break;
case T_DIVIDE:
num2 = stack.pop();
num = stack.pop();
stack.push(num/num2); /* CALC2 statement */
break;
case T_EQUALS:
num = stack.pop();
sprintf(buf_out,"= %d ",num);
stack.push(num);
break;
}
if (tok==T_STOP)
break;
}
return 0;
}
PUSHPOP.CPP
/*----- FILE: PUSHPOP.CPP --------------------------------------------*/
/* */
/* Push and pop functions for a stack of integers */
/*--------------------------------------------------------------------*/
#include <stdio.h>
#include <stdlib.h>
#include "calc.hpp"
/*--------------------------------------------------------------------*/
/* input: num - value to push on the stack */
/* action: get a link to hold the pushed value, push link on stack */
/*--------------------------------------------------------------------*/
void IntStack::push(int num) {
IntLink * ptr;
ptr = new IntLink;
ptr->set_i(num);
ptr->set_next(top);
top = ptr;
}
/*--------------------------------------------------------------------*/
/* return: int value popped from stack (0 if stack is empty) */
/* action: pops top element from stack and get return value from it */
/*--------------------------------------------------------------------*/
int IntStack::pop() {
IntLink * ptr;
int num;
ptr = top;
num = ptr->get_i();
top = ptr->get_next();
delete ptr;
return num;
}
IntStack::IntStack() {
top = 0;
}
IntStack::~IntStack() {
while(top)
pop();
}
IntLink::IntLink() { /* constructor leaves elements unassigned */
}
IntLink::~IntLink() {
}
void IntLink::set_i(int j) {
i = j;
}
int IntLink::get_i() {
return i;
}
void IntLink::set_next(IntLink * p) {
next = p;
}
IntLink * IntLink::get_next() {
return next;
}
READTOKN.CPP
/*----- FILE READTOKN.CPP --------------------------------------------*/
/* */
/* A function to read input and tokenize it for a simple calculator */
/*--------------------------------------------------------------------*/
#include <ctype.h>
#include <stdio.h>
#include "calc.hpp"
/*--------------------------------------------------------------------*/
/* action: get next input char, update index for next call */
/* return: next input char */
/*--------------------------------------------------------------------*/
static char nextchar(void)
{
/* input action
* ----- ------
* 2 push 2 on stack
* 18 push 18
* + pop 2, pop 18, add, push result (20)
* = output value on the top of the stack (20)
* 5 push 5
* / pop 5, pop 20, divide, push result (4)
* = output value on the top of the stack (4)
*/
char * buf_in = "2 18 + = 5 / = ";
static int index; /* starts at 0 */
char ret;
ret = buf_in[index];
++index;
return ret;
}
/*--------------------------------------------------------------------*/
/* output: buf - null terminated token */
/* return: token type */
/* action: reads chars through nextchar() and tokenizes them */
/*--------------------------------------------------------------------*/
extern "C"
Token read_token(char buf[])
{
int i;
char c;
/* skip leading white space */
for( c=nextchar();
isspace(c);
c=nextchar())
;
buf[0] = c; /* get ready to return single char e.g. "+" */
buf[1] = 0;
switch(c)
{
case '+' : return T_PLUS;
case '-' : return T_MINUS;
case '*' : return T_TIMES;
case '/' : return T_DIVIDE;
case '=' : return T_EQUALS;
default:
i = 0;
while (isdigit(c)) {
buf[i++] = c;
c = nextchar();
}
buf[i] = 0;
if (i==0)
return T_STOP;
else
return T_INTEGER;
}
}
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
このトピックでは、AT CALL コマンドまたは AT ENTRY コマンドを使用して、ルーチンが呼び出される直前または直後に停止する方法を説明します。 これらのコマンドの説明には、例: デバッグを行うための C++ プログラムの例 が使用されます。 これらのコマンドのいずれかを使用する前に、以下の操作を行う必要があります。
これらのコマンドのいずれかを使用する場合、関数名とともに C++ シグニチャーを組み込みます。
ブレークポイントの入力を容易にするため、ソース・ウィンドウ一番上の行にあるファイル名を上書きして、ソース・ウィンドウに PUSHPOP.CPP を表示できます。これにより、PUSHPOP.CPP がユーザーの現在の修飾されたプログラムになります。その後、以下のコマンドを入力できます。
LIST NAMES
Debug Tool は、現在修飾されているプログラムのすべてのブロックおよび 変数の名前を表示します。Debug Tool は、ログ・ウィンドウに以下の例と同じような情報を表示します。
There are no session names. The following names are known in block CALC ::> "USERID.MFISTART.CPP(PUSHPOP)" IntStack::~IntStack() IntStack::IntStack() IntLink::get_i() IntLink::get_next() IntLink::~IntLink() IntLink::set_i(int) IntLink::set_next(IntLink*) IntLink::IntLink()
ブロック名の次にコマンドを挿入することにより、キー・ストロークが節約できます。
IntStack::push(int) が呼び出される直前に停止するには、関数シグニチャーの次に AT CALL を挿入して Enter を押す と、コマンド全体がコマンド行に置かれます。これで、AT CALL IntStack::push(int) がコマンド行にあるので、次のコマンドを入力できます。
AT CALL IntStack::push(int)
IntStack::push(int) が呼び出された直後に停止するには、AT CALL コマンドの場合と同様に、以下のコマンドを入力します。
AT ENTRY IntStack::push(int) ;
IntStack::push(int) が呼び出された直後に、num が 16 である場合にのみ停止するには、以下のコマンドを入力します。
AT ENTRY IntStack::push(int) WHEN num=16;
単一の変数の内容をリストするには、変数名のところにカーソルを移動させ、PF4(LIST) を押します。値はログ・ウィンドウに表示されます。これは、コマンド行に LIST TITLED variable を入力するのと同等です。
CALC プログラムを実行し、関数 IntStack::push(int) の最初の呼び出しの中に入り込み、IntLink が割り振られる直後まで進みます。Debug Tool コマンドを入力します。
LIST TITLED num
Debug Tool は、ログ・ウィンドウに次のように表示します。
LIST TITLED num; num = 2
num の値を 22 に変更するには、num = 2 の行に num = 22 と上書きし、Enter を押してこれをコマンド行に置き、Enter を再度押してこのコマンドを出します。
コマンド行には、ほとんどの C++ の式を入力することができます。
IntStack::push(int) 内の変数 ptr の属性を表示するには、次のように Debug Tool コマンドを出します。
DESCRIBE ATTRIBUTES *ptr;
結果はログ・ウィンドウに次のように表示されます。
ATTRIBUTES for * ptr
Its address is 0BA25EB8 and its length is 8
class IntLink
signed int i
struct IntLink *nextしたがって、これは、ほとんどのクラス、構造体、および共用体の場合、ブラウザーとして機能します。
次のコマンドにより、ptr によりポイントされているクラス・オブジェクトの、すべてのデータ・メンバーの値をリストすることができます。
LIST *ptr ;
ログ・ウィンドウに、次のような結果が表示されます。
LIST * ptr ; * ptr.i = 0 * ptr.next = 0x00000000
次の例のように、コマンドとして割り当てを行うことにより、クラス・オブジェクトのデータ・メンバーの値を 変更することができます。
(* ptr).i = 33 ;
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
ユーザー・プログラムの特定の部分が、最初の数千回は 正常に機能しますが、ある条件の下では失敗するということが よくあります。このような場合、GO を入力し続けねばならなくなるので、単純な行ブレークポイントの設定はしたくありません。
例えば、main では、(例外が起こる前に) 除数が 0 である場合にのみ、T_DIVIDE で停止したいとします。この場合、次のようにブレークポイントをセットします。
AT 40 { if(num2 != 0) GO; }行 40 は CALC2 というラベルの付いたステートメントです。 このコマンドにより、Debug Tool は行 40 で停止します。num の値が 0 でない場合、プログラムは続行します。Debug Tool は、num2 が 0 の場合のみ、行 40 で停止します。
あるクラス・メソッド、例えば、クラス IntLink のクラス・メソッドに入り込んだ (ステップした) 場合、次のコマンドを入力すると、
LIST TITLED ;
this を含むリストが返されます。次のコマンドを使用すると、
DESCRIBE ATTRIBUTES *this ;
this ポインターにより指されているデータ・エレメントのタイプを見ることができます。次のコマンドを使用すると、
LIST *this ;
ポイントされているオブジェクトのデータ・メンバーのリストが得られ、次のような情報が、ログ・ウィンドウに表示されます。
LIST * this ; (* this).i = 4 (* this).next = 0x0
エレメント i を変更するには、次のコマンドを入力するか、
i = 2001;
または、あいまいな (例えば、i という自動変数もある) 場合には、次のように入力します。
(* this).i = 2001 ;
PUSHPOP.CPP ファイル内の関数 IntStack::push(int) への入り口にブレークポイントを設定したいとします。PUSHPOP.CPP は TEST を使用してコンパイルされましたが、その他のファイルはそうではありません。Debug Tool は、空のソース・ウィンドウで立ち上がります。コンパイル単位を表示するには、次のコマンドを入力します。
LIST NAMES CUS
LIST NAMES CUS コマンドは、Debug Tool が認識しているすべてのコンパイル単位のリストを表示します。
使用しているコンパイラーによっては、あるいはアプリケーションにより後から USERID.MFISTART.CPP(PUSHPOP) がフェッチされた場合、このコンパイル単位を Debug Tool が認識する場合としない場合があり、PDS メンバー PUSHPOP は、表示される場合とされない場合があります。これが表示された場合、次のコマンドを入力します。
SET QUALIFY CU "USERID.MFISTART.CPP(PUSHPOP)" AT ENTRY IntStack::push(int) ; GO ;
または
AT ENTRY "USERID.MFISTART.CPP(PUSHPOP)":>push GO
これが表示されない場合、次のように APPEARANCE ブレークポイントをセットする必要があります。
AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" ; GO ;
また、次のように、ブレークポイントを結合することもできます。
AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" AT ENTRY push; GO;
この APPEARANCE ブレークポイントの唯一の目的は、PUSHPOP コンパイル単位内の関数が最初に実行されるときに制御を得ることです。 これが起こったときに、例えば、IntStack::push(int) への入り口に、次のようにブレークポイントを設定することができます。
AT ENTRY IntStack::push(int) ;
stdout をログ・ウィンドウにリダイレクトするには、次のコマンドを出します。
SET INTERCEPT ON FILE stdout ;
この SET コマンドにより、ユーザー・プログラムからだけでなく、対話式関数呼び出しからも stdout を取り込むことができます。 例えば、コマンド行で対話式に cout を使用し、次のように入力してヌル終了ストリングを表示することができます。
cout << sptr ;
この方が LIST STORAGE を使用するより簡単かもしれません。
CICS® の場合のみ、SET INTERCEPT はサポートされません。
stdin 入力データをコマンド・プロンプトから入力可能になるように入力宛先変更するには、以下のステップを行います。
EQA1290I The program is waiting for input from stdin
EQA1292I Use the INPUT command to enter up to a maximum of 1000
characters for the intercepted variable-format file. ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
コマンド行で呼び出すことで、ライブラリー関数 (strlen など)、 あるいはプログラム関数の 1 つを対話式に開始できます。 read_token などの C リンケージ関数を開始することもできます。 ただし、C++ リンケージ関数を対話式に呼び出すことはできません。この関数は、以下の要件に準拠している必要があります。
次の例では、read_token を 対話式に呼び出します。
AT CALL read_token; GO; read_token(word);
計算器は、入力から追加のトークンが消去されたため、前とは異なる結果を作成します。
char * 変数 ptr は、印刷可能文字を含むストレージの一部をポイントすることができます。 最初の 20 文字を表示するには、次のように入力します。
LIST STORAGE(*ptr,20)
ストリングがヌルで終了している場合、次のように、対話式関数呼び出しを コマンド行で使用することもできます。
puts(ptr) ;
オフセットに基づいてストレージを表示することもできます。 例えば、ロケーション 20CD0 のオフセット 2 から 10 バイトを表示するには、次のコマンドを使用します。
LIST STORAGE(0x20CD0,2,10);
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
PUSHPOP.CPP を DLL として作成し、IntStack::push(int) と IntStack::pop() は エクスポートしてください。CALC.CPP と READTOKN.CPP を、PUSHPOP という名の DLL から IntStack::push(int) と IntStack::pop() をインポートする プログラムとして作成します。アプリケーション CALC が開始するときには、DLL PUSHPOP は Debug Tool に知られていません。次の例に示されているように、AT APPEARANCE ブレークポイントを使用して、そのコンパイル単位内に最初にコードが現れた時に、DLL 内の制御を 得るようにします。
AT APPEARANCE "USERID.MFISTART.CPP(PUSHPOP)" ; GO ;
この APPEARANCE ブレークポイントの唯一の目的は、PUSHPOP コンパイル単位内の関数が最初に実行されるときに制御を得ることです。これが起こった時に、PUSHPOP 内にブレークポイントをセットすることができます。
プログラミング・エラーの検討中に、どのようにしてこの状態になったのか、特に、関数呼び出しのトレースバックが何かを確認したいことがよくあると思います。この情報を得るには、次のコマンドを出します。
LIST CALLS ;
例えば、次のコマンドを使用して CALC の例を実行すると、
AT ENTRY read_token ; GO ; LIST CALLS ;
ログ・ウィンドウに次のように表示されます。
At ENTRY in C function "USERID.MFISTART.CPP(READTOKN)" :> read_token. From LINE 18 in C function "USERID.MFISTART.CPP(CALC)" :> main :> %BLOCK2.
これは、呼び出し元のトレースバック情報を示しています。
プログラムを変更せずに、プログラムの入り口と出口を示しながら プログラムをトレースするには、次の例に示すように、次の Debug Tool コマンドを ファイルに入れ、Debug Tool が最初にユーザー・プログラムを表示する時に、これを USE (使用) します。USERID.DTUSE(TRACE) と、次の Debug Tool コマンドを含むデータ・セットがあるとします。
int indent;
indent = 0;
SET INTERCEPT ON FILE stdout;
AT ENTRY * { ¥
++indent; ¥
if (indent < 0) indent = 0; ¥
printf("%*.s>%s¥n", indent, " ", %block); ¥
GO; ¥
}
AT EXIT * {¥
if (indent < 0) indent = 0; ¥
printf("%*.s<%s¥n", indent, " ", %block); ¥
--indent; ¥
GO; ¥
}次のコマンドを入力し、このファイルを Debug Tool へのコマンドの ソースとして使用することができます。
USE USERID.DTUSE(TRACE)
次にリストするプログラムの実行のトレース情報は、USE ファイルの実行後、ログ・ウィンドウに表示されます。
int foo(int i, int j) {
return i+j;
}
int main(void) {
return foo(1,2);
}USE ファイルを Debug Tool コマンドの入力源として指定したサンプル・プログラムの実行後、次のトレース情報がログ・ウィンドウに表示されます。
>main >foo(int,int) <foo(int,int) <main
USE ファイルを作成したくない場合、コマンド行から コマンドを入力しても同じ結果が得られます。
プログラムの実行時に、あるストレージが不意にその値を変更し、いつ、どこでこのようなことが生じたかを発見したい場合があります。関数 set_i が、呼び出し元が想定する以上に変わる次のような例を考えてみます。
struct s { int i; int j;};
struct s a = { 0, 0 };
/* function sets only field i */
void set_i(struct s * p, int k)
{
p->i = k;
p->j = k; /* error, it unexpectedly sets field j also */
}
main() {
set_i(&a,123);
}次のコマンドにより、a のアドレスを見つけます。
LIST &(a.j) ;
その結果が 0x7042A04 であったとします。このアドレスから始まる次の 4 バイトのストレージの値の変化を監視する ブレークポイントを設定するには、次のコマンドを出します。
AT CHANGE %STORAGE(0x7042A04,4)
プログラムが実行されるとき、Debug Tool は、このストレージの値が変更されると停止します。
初期化されていないストレージのエラーの検出をしやすくするため、Language Environment® (言語環境プログラム) の TEST ランタイム・オプションと STORAGE オプションを使用してユーザー・プログラムを実行します。次のような例を考えてみます。
TEST STORAGE(FD,FB,F9)
STORAGE の最初のサブパラメーターは、ヒープから割り振られたストレージのための充てんバイトです。 例えば、new という指示により割り振られたストレージは、バイト 0xFD で埋められます。このバイトがストレージにわたって繰り返されている場合、おそらく、初期化されていないヒープ・ストレージであると考えられます。
STORAGE の 2 番目のサブパラメーターは、ヒープから 割り振られたが、その後、解放されたストレージのための充てん文字です。例えば、delete を指定して解放されたストレージは、バイト 0xFB で埋められている場合があります。このバイトがストレージにわたって繰り返されている場合、おそらく、ヒープに割り振られたが解放されているストレージであると考えられます。
STORAGE の 3 番目のサブパラメーターは、新しいスタック・フレーム内の自動ストレージ変数のための充てんバイトです。このバイトがストレージ全体で繰り返されている場合、おそらく、初期化されていない自動ストレージであると考えられます。
この例で選択される値は、できるだけ早期に問題検出ができるように、奇数で大きな値です。例えば、奇数アドレスにブランチしようとすれば、ただちに例外が起こります。
初期化されていないヒープ・ストレージの例として、プログラム CALC に STORAGE ランタイム・オプションとして STORAGE(FD,FB,F9) を指定し、PUSHPOP2 のラベルの付いた行まで実行し、次のコマンドを出します。
LIST *ptr ;
次の例で示されるように、初期化されていないヒープ・ストレージの充てんバイトを見ることができます。
LIST * ptr ; (* ptr).next = 0xFDFDFDFD (* ptr).i = -33686019
ここで述べた内容に関して詳しくは、以下のトピックを参照してください。
未定義の関数の呼び出し、または NULL を指す関数ポインターを使用した関数の呼び出しは、重大エラーとなります。このような呼び出しが実行される直前に停止するには、次のブレークポイントをセットします。
AT CALL 0
Debug Tool がこのブレークポイントで停止した場合、GO BYPASS コマンドを入力してその呼び出しをバイパスすることができます。このコマンドにより、条件を起こさずにデバッグ・セッションを 続行することができます。