ラベル デバッグ の投稿を表示しています。 すべての投稿を表示
ラベル デバッグ の投稿を表示しています。 すべての投稿を表示

2012年11月14日水曜日

メモリリークを調べる

ぐるぐるまわるプログラムを書いてるとだんだんメモリ使用量が増えてくポップな現象いわゆるメモリリークが起きることがあるので調査可能なBoehm GCというライブラリを紹介します.

○ インスコ

Boehm GCのソースはここにあります.
./configure → make → make install のコンボでインストールできます.

○ 使う

4個くらいのナンセンスなプログラムを例にぐるメポップの見つけ方の例を示します.

(0) 準備
define GC_DEBUG
include "gc.h"
define malloc(n) GC_MALLOC(n)
define calloc(m,n) GC_MALLOC((m)*(n))
define free(p) GC_FREE(p)
define realloc(p,n) GC_REALLOC((p),(n))
define CHECK_LEAKS() GC_gcollect()
デバッグ用に上の文を書いときます.プログラム内でmallocした領域がBoehmの管理下に入ります. ちなみにこれを導入したプログラムをgccでコンパイルするときのオプション例は以下.
gcc -o TS TS.c -I/usr/local/include -L/usr/lib -lgc

(1) ループ内でmallocしたアドレスを何度も同じポインタ変数に代入しよう
int main(void)
{
  GC_find_leak = 1;
  
  int i; int *p;
  for(i=0;i<100;i++){
    p = (int *)malloc(100*sizeof(int));
  }

  exit(0);
  return 0;
}
GC_find_leak=1は ぐるポ検出モードです. return の前に exit してるのは return した瞬間にガベコレが働いていろいろうるさかったからです.Let Mr.Exit shut him upということです. とりあえずこれでちゃんと検出できました.やったね!v(^o^)v
実行結果はリークポイントが行数で表示されます.長いので端折りますが. ちなみにループが1回だと検出はしません.


(2) freeしないvoidのmalloc用関数を呼び出してみよう
void isalloc(int n)
{
  int *p;
  p = (int *)malloc(n*sizeof(int));
}

int main(void)
{
  GC_find_leak = 1;

  int i;
  for(i=0;i<100;i++){
    isalloc(100);
  }

  exit(0);
  return 0;
}
検出.これはループ呼び出し1回だけでも検出します.


(3) (2)のプログラムでちゃんとfreeしてみよう
void isalloc(int n)
{
  int *p;
  p = (int *)malloc(n*sizeof(int));
  free(p);
}
検出せず.

(4) (2)のプログラムで取得した領域をstaticなポインタ配列に代入して保持しよう
void isalloc(int n)
{
  int *p;
  static int num_hist=0;
  static int **p_hist;
  
  p = (int *)malloc(n*sizeof(int));
  p_hist = (int **)realloc(p_hist, sizeof(p_hist)+n*sizeof(int *));
  p_hist[num_hist++] = p;
}
これも検出せず.

ということで,領域のアドレスを保持したポインタ変数が生きてる場合は検出しませんが,解放してない領域のアドレスが手の届かないとこにいっちゃってfreeできない場合に有効みたいです.要するにこの書き方だと解放されずに上書きされたもののみ検出します.

役立つのか微妙ですが興味ある人はお役立てください.

2012年11月10日土曜日

対話式デバッガ: GDB

バグを取る、つまりデバッグの手段として一番シンプルな方法は恐らく文を出力することだと思います。
怪しい変数の値を出力させたり、途中でプログラムが止まってしまう場合は怪しい処理の前後で文を出力させたりなど。

それ以外にも、 この記事この記事の方法を使うという手もあります。

実はそれ以外にも方法があるんです!
それは、デバッガを使うということ。
gccやg++にはGDBというデバッグツールが提供されています。
その使い方を今回はご紹介致します。



2012年6月7日木曜日

Segmentation fault の原因

コンパイルは通ったのに,いざ実行してみたら"Segmentation fault"の表示.
誰でも一度は通る道だと思います.

慣れてくると,どうやってバグを取り除けばいいかわかってくるかと思いますが,
その原因となることを幾つか紹介していきます.

1,確保していない領域を使おうとしている

これが,ほぼすべての"Segmentation fault"の原因であると思います.
こんなことを書いちゃうと発生します.

int array[10];
array[15] = 20;

これは明らかに確保していない領域を使おうとしていますよね.
こんな場合だとすぐに見つけられると思いますが,たくさんの配列を使用していて,
forループで内容をいじっている場合などは問題に気づきにくい場合があります.
また,そのforループ自体では"Segmentation fault"が発生せず,不正利用している
アドレスに他のデータが重ならないとエラーが出ない場合があります.
例えば,N=50の時にはエラーが出なかったのに,N=5000にしたら"Segmentation fault"
が発生する.といった具合です.
そうなってしまったら,gdbコマンドや"printf()"を駆使して,
どこでエラーが発生するのか頑張って探しましょう.
基本的には"Segmentation fault"が発生している部分の"前"で
間違いを犯しているはずです.


2,2次元配列などでかなり大きな領域を確保しようとする

こちらは"Segmentation fault"が発生する位置が,問題を起こしている部分より"前"
にくる可能性があって,気をつけなければならないエラーとなります.
エラー自体は単純なもので,二次配列(またはそれ以上)の形で,
大変大きな領域を確保しようとした場合に発生します.

int matrix[3000][2000];

こんな具合で大きな領域を確保しようとした場合に,"Segmentation fault"が
発生します.
この解決法は単純で,配列の領域確保を"動的配列(malloc, calloc)"
で確保してやれば大丈夫です.

int **matrix;
int row = 3000, column = 2000, i;

matrix = (int **)malloc(sizeof (int*) * row);
for(i = 0; i < row; i++){
   matrix[i] = (int *)malloc(sizeof (int) * column);
}

これで解決するのですが,"Segmentation fault"が発生する部分より後ろ,
具体的には,関数内において,

void func(...){
printf("check\n");

int matrix[3000][2000];
...
}

と書いた場合に,"check"が表示されずに"Segmentation fault"が発生してしまいます.
これは,1.で示したように"前"の部分を探しても発見できず,発見が遅れるバグです.
気をつけてください.









2012年1月30日月曜日

Segmentation fault & Bus error

最初にコードを書き始めてぶつかる壁が
Segmentation fault

Bus error
だとおもう.これらの原因はたいてい(ほぼ確実に)
配列などで領域を確保していないところにまで
アクセスしようとしているということだ.

例えば,
int num[10];
の配列に対して,

for(i = 0; i < 20; i++){
   num[i] = 1;
}

このように,配列が確保していない部分まで
値を入れているとエラーが出る場合がある.

出る場合があるというのは,ここで確保していない
部分に値を入れても,その部分とどこかが競合しない
限り,エラーにならない場合がある.つまり,

int num[10], num2[30];

for(i = 0; i < 20; i++){
  num[i] = 1;
}

for(i = 0; i < 30; i++){
  num2[i] = 1;              //ここら辺でsegmentation fault
}

というプログラムを書いたとき,コメントが書いてある
部分で,Segmentation fault を起こす場合がある.
解決法としては,
*segmentation fault が起きた部分以前の部分をチェックする.
*for文で繰り返す回数を増やしてみる
などが挙げられる.

どうしてもこの部分はあっているだろうというところで
エラーが起きる場合は,そのようなところをチェックして
みると,いいかもしれない.

2012年1月27日金曜日

デバッグに関する小ネタ2

デバッグレベルというものを用いて、デバッグの度合いを段階付ける。
例えば、以下のようなコードがあるとする。

#if DEBUG_LEVEL > 10
コード1
#endif

#if DEBUG_LEVEL > 20
コード2
#endif

このとき、ソースコード上部で
#define DEBUG_LEVEL 20
と定義されていると、コード1は実行されるがコード2は実行されない。

全てのデバッグコードを実行したい場合は
#define DEBUG_LEVEL 30
などというように大きな数を定義する。

逆にデバッグコードを全て無効にするには
#define DEBUG_LEVEL 0
と定義すれば良い。 

デバッグに関する小ネタ

ソースコードの上部で以下のコードを書く。
#define DEBUG

デバッグの際のみに実行したいコードを以下のコードで挟む。
#ifdef DEBUG
...
#endif

あとは、上部のコードをコメントアウトするだけでデバッグ部分のコードは無効になる。
これで、デバッグ用プログラムと実プログラムを簡単に切り替えられる。