[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)
ここで、?と打つと使えるコマンドの一覧が出る。
とりあえず知っておくべきなのは、 あとは、関数呼び出し関係。 忘れちゃいけないのが ちょっと使用例を。
#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] (A[0]の中身を表示)
$1 = 0
(gdb) call print(A, size) (printって関数を呼んでみる)
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]