サイトマップ / 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言語講座−それ自体コンパイルできる教材を使った講座です−