サイトマップ / C言語講座>出入り口>総目次>目次:バグ対策>悪質なバグの例
/* 注意:このソースプログラムには、悪質なバグがあります。コンパイルして実行すると、画面に異常な文字が表示されたり、最悪の場合、システムがクラッシュして、暴走します。もし、実行するなら、下の説明文を充分読んでからにして下さい。
Cのコンパイラは、特に、ポインタに対しては、文法チェックが甘くできています。このため、コンパイルをうまく通り抜け、実行時にエラーとなることがあります。その中でも、特に悪質なのは、バグのあるステップが実行されても、一見正常に動作したり、しばらくしてから動作が異常になったり、最悪の場合システムがクラッシュするバグです。バグのあるステップと異常の現れるステップが、別の箇所なのでバグの発見が困難です。
今日は、4つの関数が出てきますが、最後の関数の中にこの種のバグがあります。これらの関数は全て、sprintf( ) で配列に文字列を書き込みます。配列の確保の仕方がそれぞれの関数で違います。
関数の説明の前に、sprintf( ) について説明します。
#include <stdio.h> int sprintf(char *s, const char *format, ...); 使用例:sprintf(s, format, ...); 実行結果 戻り値 成功した場合 出力した文字数 失敗した場合 EOF
sprintf( ) は printf( ) に良く似た関数で、s という char 型の配列に文字列を書式に従って書き込んで、文字列の末尾に自動的にヌル文字を付けます。では、今日の関数について説明します。
void Func1(void);
関数の外側で宣言した配列 str1[ ] に書き込む。
void Func2(char *str2);
メインルーチンで宣言した配列 str2[ ] に、ポインタを使って書き込む。
char *Func3(void);
Func3( ) の中で malloc( ) で割り付けられた配列に書き込み、配列へのポインタを戻り値として返す。
char *BuggyFunc(void);
BuggyFunc( ) の中で宣言した配列に書き込み、その配列へのポインタを戻り値として返す。
最後の関数では、自動変数を指すポインタが返ってきます。関数を抜け出す直前のところでは、ポインタは正しくデータを指しています。しかし、メインルーチンに戻ると、その自動変数はいずれ破壊されます。破壊されるまでは、ポインタは、たまたま、正しくデータを指しています。
プログラムを実行すると、そのステップを通って、しばらくしてから、そのポインタは何か不明のものを指すようになります。動作がおかしくなる可能性があります。システムがクラッシュするかも知れません。コンパイルして、実行して、もし、プログラムが暴走したら、パソコンをリセットして下さい。 */
#include <stdio.h> #include <stdlib.h> /* malloc( ) を使うのに必要 */ #define BUFFSIZE 100 void Func1(void); void Func2(char *str2); char *Func3(void); char *BuggyFunc(void); void main(void); static char str1[BUFFSIZE]; /* この行以降で読み書きできる */ /* Func1 ( ) で使用 */ /* 静的変数に書き込む */ void Func1(void) { static int n = 1; /* str1 は数行上で宣言されている配列の名前 */ /* 従って、その配列へのポインタになる */ sprintf(str1, "%d Func1( )", n); n++; } /* main で宣言した自動変数に書き込む */ void Func2(char *str2) { static int n = 1; /* str2 は main で宣言されている配列の名前 */ /* 従って、その配列へのポインタになる */ sprintf(str2, "%d Func2( )", n); n++; } /* malloc で割り当てられたメモリに書き込む */ /* そのメモリへのポインタを戻り値とする */ char *Func3(void) { static int n = 1; char *s; /* メモリを割り付ける */ /* メモリはfree( ) が呼ばれるまで、存在する */ s = (char *)malloc(BUFFSIZE * sizeof(char)); sprintf(s, "%d Func3( )", n); n++; return (s); } /* 自動変数に書き込み */ /* 自動変数へのポインタを戻り値とする */ char *BuggyFunc(void) { static int n = 1; char str[BUFFSIZE]; /* 関数から抜けると破壊される */ sprintf(str, "%d BuggyFunc( )", n); /* ここはバグではない */ n++; return (str); /* ここが悪質なバグ */ } /* ▲▲▲▲▲▲▲▲ */ void main(void) { int i; char *s; /* 自動変数 */ char str2[BUFFSIZE]; /* Func2( ) で使用 */ for (i = 0; i < 4; i++) { Func1( ); /* 静的変数に書き込む */ puts(str1); Func2(str2); /* main で宣言した自動変数に書き込む */ puts(str2); s = Func3( ); /* malloc で割り当てられたメモリに書き込む */ puts(s); free(s); s = BuggyFunc( ); /* 自動変数に書き込み */ puts(s); /* 自動変数へのポインタを戻り値とする */ printf("\n"); } } |
/* 実行結果はいかがでしたか。もしかすると、正常に動作したという方も、おいでかも知れません。しかし、そうでははく、正常に動作しているように見えるにすぎません。BuggyFunc( ) で確保した配列が、メインルーチンに戻っても、たまたま破壊されずに残っていて、この配列へのポインタでアクセスできただけです。
異常な文字が表示されたという方もおいででしょう。不幸にも暴走して、リセットしなければならなかった方もおいででしょう。どういう結果になるかは予測できません。こういうことを、結果は不定だといいます。
各関数の中で、その関数に何回入ったかを数えるカウンタを、
static int n = 1;
として宣言しています。
関数の { } の中で、static を付けて宣言されているので、{ } の中がこの変数のスコープです。従って、変数の名前が同じでも、それぞれは別物です。コンパイル時に1に初期化され、その関数が呼ばれる度に値を1増やし、プログラムが終わるまで存在し続けます。 */
/* (C) 2000- YFプロ. All Rights Reserved. */ 提供:C言語講座−それ自体コンパイルできる教材を使った講座です−