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

青い直線

サイトマップ / C言語講座出入り口総目次目次:バグ対策>悪質なバグの例