初心者の初心者による初心者のためのC言語講座

「core dump」したらお手上げだぁ?





コンパイル時のエラーはありがたい!


突然ですが、みなさんはC言語意外でも何らかの言語でプログラミングを したことはありますか?

私は、プログラミング自体に興味があったので、C言語意外にも PerlJavascriptなどに手を出した経験が あります(もちろんFORTRANBasicなどもやったことはありますが…)

個人的な意見になってしまいますが、私は PerlJavascriptなどがあまり好きではありません (Perl や Javascript 使いの方、ゴメンなちゃい!)
どうしてかというと、これらの言語にはコンパイルというものが存在 しないからです。コンパイルが存在しないと何が問題か?というと、

実行して初めて記述内容にエラーがあることが判明するからです。

具体的にどんなエラーかが分かれば良いのですが、エラーが発生して うまく実行できなかったことは分かるのですが、実際にどんなエラー なのかが分からない場合が多いのです。

一方、C言語にはコンパイルというものが 必ずついてきますので、実行する前にエラーがあることが分かりますし、 最低でもソース中のどの部分でエラーがあるのかを知ることができます。

でも、コンパイルはうまく通ったのに、 いざ実行してみると

セグメントエラー (coreを出力しました)

とか
Segmentation fault(core dumped)

とかいう悲しいメッセージが出たことはありませんか?

これが結構やっかいなのです。 ある程度プログラムが走ってから止まればまだしも、なんのアクションも 示さないで単に上記のエラーが出ると、 結局、どの部分に問題があるのか? が分からないのです。ある程度の経験を積めば大体の位置は予想できるの ですが、初心者の頃は全く分からなかったりして、泣きたい気分で いっぱいになってしまうのです。

だから、コンパイルエラーばかりで イライラしている方、

コンパイルでエラーが発見できるのはありがたいんですよ!

このページでは、「core dump」してしまった際の デバッグの手法について説明します。 特にデバッグツールとして有名な 「gdb」の簡単すぎる使い方 について説明します。





こんなプログラムを作ったとします…


「cat コマンドを作ってみよう!」で 紹介したプログラムに似たものを作ってみましょう!

以下に示すプログラムを作成してみてください


  1: #include <stdio.h>
  2: 
  3: int main( int argc, char *argv[] )
  4: {
  5:   FILE *fpr;
  6:   char buf[BUFSIZ];
  7: 
  8:   if( argc < 2 ){
  9:     /* もしファイル名が指定されなかったらプログラムを終了する */
 10:     return( -1 );
 11:   }
 12:   
 13:   if(( fpr = fopen( argv[1] , "r" )) != NULL ){
 14:     /*指定したファイルがオープンできれば*/
 15:     /*ファイルの内容を表示する*/
 16:     while( fgets( buf, sizeof( buf ), fpr ) != NULL ){
 17:       fprintf( stdout, "%s", buf );
 18:     }
 19:   }else{
 20:     /*指定したファイルがオープンできなければ*/
 21:     /*何もしない*/
 22:   }
 23: 
 24:   /*ファイルを閉じる*/
 25:   fclose( fpr );
 26: 
 27:   return ( 0 );
 28: }
gdbcore.c


このプログラムは単に 引数として指定されたファイルを表示する だけのプログラムです。
コンパイルは

cc     -g     -o     gdbcore     gdbcore.c

としてコンパイルしてくだい。この「-g」 オプションは「デバッグ情報を付ける」 というオプションです。

コンパイルに成功したら、実際に実行してみましょう!
とりあえず、このプログラムのソースを引数として与えて 実行してみましょう!



> gdbcore gdbcore.c
#include <stdio.h>

int main( int argc, char *argv[] )
{
  FILE *fpr;
  char buf[BUFSIZ];

  if( argc < 2 ){
    /* もしファイル名が指定されなかったらプログラムを終了する */
    return( -1 );
  }
  
  if(( fpr = fopen( argv[1] , "r" )) != NULL ){
    /*指定したファイルがオープンできれば*/
    /*ファイルの内容を表示する*/
    while( fgets( buf, sizeof( buf ), fpr ) != NULL ){
      fprintf( stdout, "%s", buf );
    }
  }else{
    /*指定したファイルがオープンできなければ*/
    /*何もしない*/
  }

  /*ファイルを閉じる*/
  fclose( fpr );

  return ( 0 );
}
>


正常に動きましたね?

それでは、今度は存在しないファイルを引数として指定して実行して みましょう!
例として「ls.txt」を引数として 指定してみましょう。ファイル名はどんなものでも構いませんが、 存在しないファイルを指定 してください。


> gdbcore   ls.txt
セグメントエラー (coreを出力しました)
>


core dump してしまいましたね。エラーメッセージはお使いの環境によって、 英語で表示されているかもしれません。

コンパイルはうまく通ったのに、正常に実行されませんでした。ソース中に 問題点があることは予想できますが、どこに問題があるのでしょうか?
一見すると問題点はなさそうなんですけどねぇー。まあ、経験のある プログラマーの方だったら容易に問題点を発見できるでしょう。






---- デバッグ方法 ----
その1

「fprintf」を使う

もしどこに問題があるのかに見当がつかなければデバッグをして 問題のある部分を探しださなければなりません。

デバッグの手法としてはいろいろ考えられますが、まず最初に考えられるのは 「fprintf」関数を使う方法です。

これは、一番簡単で分かりやすい方法です。具体的には適当な位置に
fprintf( stderr, "Check\n" );
という記述を置き、実行した際に「Check」 という文字が画面に表示されたら問題点はそこ以降にあり、 表示されなければ、問題点はそこ以前にあるということになります。

この作業を繰り返し、最終的に問題のある記述を探しだすわけです。
では、実際にやってみましょう!     まず、どこでも良いのですが大体の見当をつけます。とりあえず 変数を宣言した段階でエラーが発生していないか?   もしくは 引数を処理している段階で問題が発生していないか?を確認するために、 上に示した ソース中の12行目 に「fprintf( stderr, "Check\n" );」 という記述を挿入します。

先ほどと同じようにコンパイルして実行してみてください。多分 下のような感じになると思います。

> gdbcore   ls.txt
Check
セグメントエラー (coreを出力しました)
>


Check」という文字が画面に表示 されましたね?   このことから、 ソース中の 1〜12行目に問題はない ということになります。

今度は先ほど挿入した記述を23行目 に変えてみてください。 実際に実行すると、先ほどと同じように 「Check」という文字が表示されます。

今度は26行目に記述してみてください。 実行すると

> gdbcore   ls.txt
セグメントエラー (coreを出力しました)
>

Check」という文字が 表示されなくなりました。このことから 問題点は23〜26行目にあるという ことになります。実際には25行目

fclose( fpr );

という記述に問題があります。





---- デバッグ方法 ----
その2

デバッグツール「gdb」を使う


次の方法として、デバッグツールである「gdb」 を使う方法について説明しましょう。私も gdb を完全に使いきっている わけではないのですが、ここでは簡単にコア・ファイルの解析方法を 紹介し、実際にどの部分でコア・ダンプしたのかを調べます。

では実際に「gdb」を動かしてみましょう。 gdb には引数として実行ファイルとコアファイルを指定します。

gdb     (実行ファイル)     (コアファイル)

このように指定します。今回の「gdbcore」という実行ファイルに関しては 以下のようにします。


> gdb gdbcore gdbcore.core  
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-unknown-freebsd"...
Core was generated by `gdbcore'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /usr/lib/libc.so.4...done.
Reading symbols from /usr/libexec/ld-elf.so.1...done.
#0  0x280c8948 in fclose () from /usr/lib/libc.so.4
(gdb) 

上記のように、あーだ・こーだといろいろなメッセージが出た後 「(gdb)」というプロンプト(?)が でます。ここで「where」コマンドを打って みましょう。


> gdb gdbcore gdbcore.core  
GNU gdb 4.18
Copyright 1998 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i386-unknown-freebsd"...
Core was generated by `gdbcore'.
Program terminated with signal 11, Segmentation fault.
Reading symbols from /usr/lib/libc.so.4...done.
Reading symbols from /usr/libexec/ld-elf.so.1...done.
#0  0x280c8948 in fclose () from /usr/lib/libc.so.4
 (gdb) where 
#0  0x280c8948 in fclose () from /usr/lib/libc.so.4
#1  0x8048634 in main (argc=2, argv=0xbfbffa74) at gdbcore.c:25
#2  0x8048511 in _start ()
(gdb) 

上のようなメッセージが出たと思います。全ての中身を知ろうとすると 大変ですが、 で示した部分に注目してください。

まず「gdbcore.c:25」 というメッセージが出ています。これは、ソースファイル 「gdbcore.c」中の 25行目fclose()関数を呼び出し、この関数を 実行した際に問題が発生したということを示しています。

先ほどのfprintfを使う方法と同じく、 25行目fprintfで問題があることが分かりましたね?





最後に…


問題のある部分を探し出す方法については理解できたでしょうか? ソフトウェアの作成途中は要所要所で 「fprintf」を挿入して、常に正常に 動いているかを確認する癖をつけた方が後々便利です。

fprintf」の方法でうまく探せ出せなかった 場合に、「gdb」を使って問題点を修正すれば 完璧です。

えっ?「まとめはいいから、何で fclose に問題があるのかを説明しろ!」 ですって?

まあ、それは皆さんで考えて欲しいところなのですが、今回だけ特別に 解説しましょう。ソース25行目fcloseは、ファイルオープンに失敗しても 実行されます。この場合 fclose 関数に渡される FILE型変数 fpr には NULLポインタが入っていますので、fclose はNULLポインタを閉じようと します。そんなことはできませんので、プログラムが飛んでしまったわけ です。

したがって 25行目の fclose関数は本来ならば18行目と19行目の間に 挿入されるべきなのです。




質問等については ここ にメールを送ってください


CONTENTSに戻る