void change_lights( int lights[5][5], int X, int Y )
{
int A;
for(A = Y-1;A <= Y+1;A++){ /* (X,Y)とその上下の0を1に1を0に */
switch( lights[A][X] ){
case 0:
lights[A][X] = 1; break;
case 1:
lights[A][X] = 0; break;
default: break;
}
}
これは、何人かの学生から提出されたレポートである。
確かに動作する。しかし、lights[A][X] の添字がどうもおかしな範囲を
取っているように思える。
そこで、この関数に次のような行を追加してみた。
#include <assert.h>
void change_lights( int lights[5][5], int X, int Y )
{
int A;
for(A = Y-1;A <= Y+1;A++){ /* (X,Y)とその上下の0を1に1を0に */
assert( 0 <= A && A <= 4 );
assert( 0 <= X && X <= 4 );
switch( lights[A][X] ){
case 0:
lights[A][X] = 1; break;
case 1:
lights[A][X] = 0; break;
default: break;
}
}
このプログラムはこの行で停止してしまうのだ。
Assertion failed: 0 <= A && A <= 4, file main.c, line 10 Abort (core dumped)つまり、このプログラムは、アルゴリズムが間違っているにもかかわらず、 たまたま今まで動作していたのである。 処理系によってはエラーが出て止まってしまっていただろう。
/* * int assert(int expression); * このマクロは、プログラムに診断を挿入するとき有効です。 * 実行中に expression が偽(ゼロ)になった場合、 assert() は、 * 標準エラー出力にエラーを表示し、強制終了します。 * -DNDEBUG を指定してコンパイルすると、アサーションは * プログラムにコンパイルされません。 */
assert はさまざまなバグの予防に使用できるが、 配列とポインタの操作に限って用いても、 多くの誤りを検出できるはずである。
配列やポインタの中身を操作する時に、変数でその要素番号を 指定している場合には、添字が正しい範囲を取っていることを、 assert を用いて積極的に確認して欲しい。
また、デバッグが終ったプログラムにも assert は残しておくこと。 その行の周辺を修正したときに、新たなバグが発生するかも 知れないからだ。
バグの検出よりも速度を重視する場合は -DNDEBUG オプションを つけてコンパイルし直せば、assert マクロは完全に無視される。 assert を残しておいて不利になることは何もない。
各自、不適切なメモリアクセスを防ぐための assert() マクロを 挿入し、たまたま動くプログラムではなく、論理的なエラーの ないプログラムを再度提出せよ。