[an error occurred while processing this directive]
[an error occurred while processing this directive]
Debug tips (C/C++)
Cを使う限り避けて通れないのがデバッグの茨の道…
まずはprintfデバッグ
デバッグの基本は、どの変数にどこで何が入ってるかをちゃんと把握すること。
変な挙動があったら、とりあえずあちこちにprintfを入れてみる。
このとき、改行も出力させないと、printfの直後でsegmentation faultが起こる場合などはちゃんと結果が表示されない。
int main(){
int A[3];
printf("a");
A[3] = 1;
return 0;
}
これを実行すると、結果は単にSegmentation Faultと出るだけで、どこがおかしいかわからない。
これを
printf("a\n");
又は
printf("a");
fflush(stdout);
とすると、ちゃんとaを表示した後で落ちてくれる。
ちょっと賢くprintf
デバッグで、変数の名前と値を表示したい時
#define OUT(A) printf("%s = %d\n",#A, A)
というマクロを定義しておくと、
int main(){
int i = 3;
OUT(i);
}
とするだけで、
i = 3
みたいな出力が得られる。これは%dから分かるように整数にしか使えないけど、C++使ってるなら
#include<iostream>
#define OUT(A) std::cout << #A << '='<<A << std::endl;
int main(){
int i = 3;
OUT(i);
}
とすると、intでもdoubleでもstringでも使えます。
assert入れて
例えばプログラム中で、「ここはxが0にはなりえない、なったらやばい」
と思ったら、迷わず下の一行を書きましょう。
#include <assert.h>
...
assert(x != 0);
こうすると、もしxが0になったときにちょうどそこで落ちてくれます。
次のgdbと組み合わせると、デバッグの強い味方になります。
gdb使おう
GNUのデバッガ・gdbの使い方。簡単に。
emacsが使える人なら、M-x gdbとしてemacs上で使うのがお勧めです。
まず、-gを付けてコンパイル。
$ gcc test.c -g -o test
で、gdbに食わせる。
$ gdb test
(gdb)
ここで、?と打つと使えるコマンドの一覧が出る。
とりあえず知っておくべきなのは、
- run ... プログラム実行。引数も指定できる
- break ... break main(関数mainが呼ばれたら中断)みたいに、プログラムの中断地点を指定。
- continue ... breakから復帰
- step ... breakしたとこから一行ずつ実行
- list ... 現在実行されてるコードを表示
- print ... print aとすると、変数aを表示してくれる
- call ... 関数実行
あとは、関数呼び出し関係。
- where ... 現在呼び出されてる関数構造を表示
- up / down ... 関数呼び出しの上下を移動する
- thread ... スレッド選択
忘れちゃいけないのが
ちょっと使用例を。
#include <stdio.h>
void init(int *A, int size){
int i;
for(i = 0; i < size; i++)
A[i] = i * i;
}
void print(int *A, int size){
int i;
for(i = 0; i < size; i++)
printf("A[%d] = %d\n", i, A[i]);
}
int main(void){
int i;
int size = 4;
int A[4];
init(A, size);
A[1000] = 10;
print(A, size);
}
実行したら、多分A[1000] = 10;の行で落ちます。
そこでこれをgdbで実行。
$ gdb test
(gdb) run
Program received signal SIGSEGV, Segmentation fault.
main () at test.c:22
(gdb) where
#0 main () at test.c:22
(gdb) list
21 init(A, size);
22 A[1000] = 10;
23 print(A, size);
(gdb) print A[0]
$1 = 0
(gdb) call print(A, size)
A[0] = 0
A[1] = 1
A[2] = 4
A[3] = 9
(gdb)
こんな感じで、、変数中身を簡単にのぞける。
それとか、実行前にbreakを指定しておくと、Segmentation fault以外でも途中で止めることができる。
さっきと同じソースで
(gdb) break init
とすると、init()に入ったとこで止まるから、そこからstepで一行ずつ進めたりできる。
コンパイルは-Wallを付けよう
return忘れにご注意。
次のプログラムは、コンパイルを通ってしまいます。(gcc version 2.95.3 20010315 (release))
#include<stdio.h>
typedef struct kei_{
int i;
} kei;
kei *new(){
kei *k;
k = (kei *)malloc(sizeof(kei));
k->i = 23;
/* ここにreturn kがない */
}
int main(){
kei *k;
k = new();
printf("%d\n", k->i);
}
これを実行すると、僕の環境では23と表示されましたが、コンパイルオプションを-O3に変えると動かなくなりました。
こういうたちの悪いバグを防ぐには、
% gcc -g -Wall test.c
としてコンパイルすると、
warning: control reaches end of non-void function
と警告を出してくれます。
segmentation faultしたら-lefence
僕みたいに経験不足&&注意力散漫なプログラマは、変な番地に書きこんじゃったりfree()しすぎたりと
Segmentation fault系のエラーに悩まされがちですが、それをちょっと楽にする方法。
こういうセグフォ系のエラーのいやらしいところは、実際にバグったコードを書いてる部分では症状が出なくて、
それと遥か離れたとこで不正なアクセスが起こってたりすること。
このライブラリはmalloc()とfree()を上書きすることで、変な番地への書き込みや、二重free()などを程度検出してくれ、
エラーが起こった場所で"正しく"落ちてくれるようになります。
まず、efence (
Electric Fence)ライブラリを入れます。(debianやgentooにはパッケージがあります)
あとはコンパイルの時に
gcc test.c -lefence
とするだけです。出来たバイナリは普通に実行して下さい。
実行時に"Electric Fenceなんとか"ってメッセージが出ればちゃんとリンクされてます。
debug の時だけ print するマクロ
こんなマクロを書いておくと良い。
#ifdef DEBUG
#define DPRINT(s...) fprintf(stderr, s)
#else
#define DPRINT(s...)
#endif
コードのほうはこんなふうに書く。
DPRINT("debug message %d\n", i);
そのうえで、debug したいときは #define DEBUG すると、メッセージが表示される。
安定してきたら、define DEBUG をはずせばよい。
(gcc のオプションで -DDEBUG とすると、DEBUG というマクロ変数が定義されるので、これを使っても良い)
フォーマットつきデバッグ関数。
void err_printf(const char *format, ...){
va_list arg;
char buf[MAX_LEN];
va_start(arg, format);
vsprintf(buf, format, arg);
va_end(arg);
fprintf(FP, buf);
}
[an error occurred while processing this directive]