金子邦彦研究室プログラミングC 言語によるアルゴリズムとデータ構造Cコンパイラ,デバッガ

Cコンパイラ,デバッガ

1 コンパイラ

高級言語でプログラムを書いて,そのプログラムを実行させるには,プログラム を機械語に変換しなければなりません.この変換作業を行うのがコンパイラです. 使用するコンパイラは,言語により異ります.もちろんシステムにも依存します. C,C++言語のコンパイラとしては,FreeBSDでは,gcc, g++というコンパイラが使 用できる.Solarisでは,gccや g++も使用できるが,CCというコンパイラも 使用できる.コンパイラは以下の手順でプログラムをコンパイルします.以下, ある言語で書いたプログラムをソースプログラムと呼びます.

  1. プリプロセッサ
    ソースプログラム中のコメントアウトを除去したり,#で始まる行 (#define, #include, #ifdef)などを処理します.
  2. コンパイラ本体
    1.で生成されたファイルからアセン ブリ言語ソースファイルに変換します.アセンブリ言語とはシステムの プロセッサが実行することができる命令でプログラムを作成する言語で す.アセンブリ言語はシステムに依存します.アセンブリ言語ソースファ イルは,テキスト形式であるのでシステムのアセンブリ言語を勉強すれ ば読むことは可能です.
  3. アセンブラ
    2.で生成されたアセンブリ言語ソースファイルを機械語 で表したオブジェクトファイルに変換します.オブジェクトファイルと は,アセンブリ言語ファイルの文字列をすべて0か1の数字の並びに置き 換えてバイナリ形式にしたファイルのことです.
  4. リンカ
    最終的に,プログラムを実行できるようにするためにオブジェクトファ イルから,ロードモジュールを生成します.ロードモジュールとは複数の オブジェクトファイルやライブラリが結合されて実行可能となったもの です.

1.1 gcc, g++ コンパイラの使い方

gccはC言語用,g++はC++言語用コンパイラです.以下 gccを用いてコンパイル方 を説明するが g++ でも同様に使用できる.sample.cというCプログラムソー スファイルをコンパイルするときは以下のようにします.
  % gcc sample.c
このようにすると,a.outという実行ファイルが作成される.ここでは,上で 述べた1.から4.までの処理をすべて行っている.gccではオプションを付け るか,コンパイルエラーがでない限り,リンカまでの処理をすべて行う.
実行ファイル名をa.out以外の名前にしたい場合は以下のように -o オプション を付けてコンパイルします.
  % gcc sample.c -o sample
ソースプログラムをコンパイルして出来た実行ファイルを実行する場合は,以下 のようにします.(ここで実行ファイルの名前は sample とします)
  % ./sample
以下に,gccのオプションをいくつか説明する.

1.2 コンパイルエラーの対処法

コンパイルを行うと必ずと言っていいほどエラーがでます.エラーに対処するに は,エラーがでている場所と,どうしてエラーがでているかをつきとめなければな りません.コンパイラはエラーがでている場所を教えてくれて,それがどういう エラーかも教えてくれます.ただしコンパイラが正確にエラーの場所と種類を教 えてくれるとは限りません,エラーの場所とその内容を探すには,経験が重要で す.以前に見たことがあるエラーならば,それの対処法はだいたいわかります. これからコンパイルエラーの例を挙げて,コンパイルエラーの意味と対処法を説 明します.
以下のようなC言語のソースファイルを sample.c とします.(左端の数字は行番 号を表していて実際のファイルには存在しません.)
1.  int main(){
2.    int i = 3;
3.    int j = 5;
4.    k = i + j;
5.  }
このソースをコンパイルすると以下のようなエラーがでます.
mine@eric[src]% gcc sample.c -o sample
sample.c: In function `main':
sample.c:4: `k' undeclared (first use in this function)
sample.c:4: (Each undeclared identifier is reported only once
sample.c:4: for each function it appears in.)
このように,コンパイラはエラーコードのファイル名と場所,その内容を教えて くれるのでそれを見て,ソースファイルを修正すればよい.この場合は,変数 k を宣言すれば,コンパイルエラーは解決される.

2 デバック

コンパイルができてプログラムが実行できも,最初から快調に動くことはまずあ りません.大規模なプログラムになればなるほどこの事はよく当てはまります. つまり,コードの中にバグがあるのです.デバックとはこのバグを 見つけ出し,バグを直すことを言います.

2.1 バグの再現

バグが起ったらそのバグは,どういった場合に発生するのかを詳しく突き止める 必要がある.たとえば,ある入力を与えるプログラムならば,どのような入 力のときにバグが起るかを突き止めます.そうすることにより,バグを再現しな がらデバックすることが簡単になる.
また,バグを見つけると,そこの コードを変更するわけですが,自分がここが怪しいと思ってコードを書き直して も,バグが無くなるとは限りませんし,もしかしたらもっとひどいバグになるか も知れません.またコードを書き直したら,違うバグがでてきたということもあ ります.ですから,バグがあってそのコードを書き直すときは,ソースのバッ クアップを取ることによりバグを再現できるようにしてください.そうすること で,違ったバグがでてきたとしてもソースを見比べたり,実行結果を比較するこ とによりバグの箇所がわかることがあります.また,ソースのバックアップを取っ ておくことは,大規模なプログラムをデバックしていてどこをどう変更したかわ からなくなったときなどにも役に立ちます.

2.2 自己検証コード

バグの場所を見つけるのに,一番簡単な方法はいろいろな情報を出力させること です.例えば,プログラムが途中で止まってしまうときなどには,どこまでプ ログラムが動いているかを知ることが重要です.そこで,プログラム中にメッセー ジを表示させるコードをいくつか書いて,実行中にどのメッセージが表示された かを見ることにより,プログラムの停止箇所がわかります.また,値を計算する するプログラムの解がおかしな値になっていたとしたら,計算途中の値などを表 示させることも有力な手がかりとなる.多くのバグは,簡単なプログラミン グのミスから生じますので,バグの場所がわかれば,ほとんどの場合バグは簡単 に解決します.

2.3 デバッガ

デバッガとは,デバックの手助けをしてくれるツール.デバッガを使うと, プログラムを1行づつ実行したり,特定の行でプログラムを停止することができ ます.また実行を停止した時点での変数の値や関数に評価されたあとの値などの情 報も見ることができる.デバッガを使ってプログラムを実行しているときにプロ グラムが異常終了すると,デバッガはその位置を覚えていてくれて,どこでプログ ラムが停止したかを即座に知ることができる.デバッガはこのような高度な機 能を持っているので,デバッガを使うことによりデバック作業の効率は大幅に増 加します.
デバッガもコンパイラと同様で,言語やマシンに依存したものです.またデバッ カはコンパイラにも依存します.gccでコンパイルしたものは gdbというデバッ ガが使える.Solarisの CCコンパイラでコンパイルしたものだったら dbxとい うデバッガが使用できる.以下,gdbの使い方について説明する.

2.4 gdb(デバッガ)の使い方

gdbは以下のようにして使う.
% gdb sample
(gdb) quit

gdbコマンドには,以下のようなものがあります.
(gdb) info file
(gdb) file filename
(gdb) run
(gdb) kill
(gdb) break [filename:]num
(gdb) clear [filename:]num
(gdb) info breakpoints
(gdb) step [num]
(gdb) next [num]
(gdb) continue
(gdb) finish
(gdb) where
(gdb) print exp
(gdb) list
(gdb) help comand

この他にもたくさんのコマンドがありますので,helpで調べてください. それでは,gdb の使い方の例を挙げてみます. 以下のような sample.cと output.cというプログラムがあるとします.
sample.c
1. void output(int value)
2.
3. int
4. main(){
5.   int value = 100;
6.   int i;
7.   for(i = 0 ; i < 5 ; i++)
8.     output(value);
9. }
output.c
1. #include<stdio.h>
2.
3. void
4. output(int value){
5.   printf("%d\n",value);
6. }
output.c中の outputは,引数として与えられた値を標 準出力に出力する関数であり,sample.c 中の main関数の中で引数として value (= 100)を与えらて5回呼び出されている.このプログラムを実行すると 100が 5回標準出力に出力される.このプログラムは正しく動くのでデバッガを使う必要 はありませんが,デバッガでどういうことができるかをこれから説明する.
  1. まずこれらのファイルをコンパイルします.この時にコンパイルオプショ ン -gを付けます.このオプションを付けないと gdbでデバック作業は行 えません.以下のようにコンパイルします.
    % gcc -g -c sample.c output.c
      オブジェクトファイル sample.o output.oが生成される.
    % gcc -g sample.o output.o -o sample
      実行ファイル sampleが生成される.
  2. 出来た実行ファイルを普通に実行すると以下のようになる.
    mine@eric[src]% ./sample
    100
    100
    100
    100
    100
    
  3. それでは,今度は gdbを使って sampleを実行してみる.以下のように して実行する.メッセージが表示されて,(gdb)となれは gdb モードになっています.
    mine@eric[src]% gdb sample
    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"...
    (gdb)
    
  4. まずブレークポイントを指定します.sample.cの8行目にブレークポ イントを指定します.
    (gdb) break 8
    Breakpoint 1 at 0x8048504: file sample.c, line 8.
    
  5. プログラムを実行する.
    (gdb) run
    Starting program:/.amd_mnt/shiozaki/home/4f/mine/study/free/CC/src/sample
    Breakpoint 1, main () at sample.c:8
    8 output(value);
    
    ブレークポイントである sample.cの8行目で停止しいています.
  6. 変数 iと valueの値を表示す.
    (gdb) print i
    $1 = 0
    (gdb) print value
    $2 = 100
    
  7. ステップ実行する.
    (gdb) step
    output (value=100) at output.c:5
    5 printf("%i\n", value);
    
    output.cの5行目,関数 outputの中で停止している.
  8. どうのようにして関数outputに来ているか表示す.
    (gdb) where
    #0  output (value=100) at output.c:5
    #1  0x8048510 in main () at sample.c:8
    #2  0x8048459 in _start ()
    
    これは下から読みます.つまりスタックを表示している._start()はシ ステム側のものです.#1からがユーザプログラムです.現在 sample.c 8行目,main関数から,output.cの関数 outputが呼び出され,output.c の5行目で停止していることを表している.
  9. continue コマンドを実行する.
    (gdb) continue
    Continuing.
    100
    
    Breakpoint 1, main () at sample.c:8
    8 output(value);
    
    2行目でvalue(=100)を出力している.ブレークポイントである sample.cの8行目で停止している.
  10. nextコマンドを実行する.
    (gdb) next
    100
    7 for( i = 0; i < 5; i++ )
    
    前の stepコマンドのとの違いをみてください.output関数の中を処理し たあとに,sample.cの次の行で停止している.value(=100)が出力され ているいます.
  11. print コマンドで関数の値を評価してみる.
    (gdb) print output(10)
    10
    $3 = void
    
    関数 outputの引数として10を与えた場合の式を評価している.1行目で 10が出力されている.2行目の値は関数 outputの返り値です.
  12. ブレークポイントを消去して,最後までプログラムを実行する.
    (gdb) clear 8
    Deleted breakpoint 1
    (gdb) continue
    Continuing.
    100
    100
    100
    
    Program exited with code 04.
    
    ブレークポイントを削除して,continueコマンドを使用すると,プログ ラムの最後まで実行している.value(=100)を3回出力してプログラム を終了している.