...ing logging 4.0

はてなブログに移行しました。D言語の話とかいろいろ。

assert

研究の実験をするときには,何かしらのプログラミングをすると思う.
そしてバグが出ないことを祈りながら,恐る恐る実験をする・・・.
うまく実験が終わっても,まだ安心はできない.
ログはちゃんと取れた?
おかしなデータはないか?
データの検証をしてみよう.


・・・あれっここのデータ,他の結果と整合性が取れないぞ!?


なーんてことになったら大変ですよね (・人・)ナムー
なぜこんなことになってしまったのか?
自分が作ったプログラムのテストが不十分だった? テストのやり方が間違っていた? 想定していない条件や実行パスがあった?
理由は様々かもしれませんね.
でも,すべてに共通して言えることは,その論理的バグはプログラムをクラッシュ(強制終了)させなかった,という事実.
そうです,どうせおかしいなら何事もなく実行してくれるより,むしろ途中で止まってくれた方がいい.


トラブルの報告は早ければ早いほどいいのです.


そんなあなたにお薦めするのが今回のテーマ,「Assertion(表明)」.
ついでに VC++ の Debug/Release はどうやって使い分けるのかも説明します.
さあ,実験を成功させるためにひとつお勉強しようではありませんか.


なお,今回はC言語を読める人が対象です.言語の詳しい説明まではしません.
また,他の言語にも Assertion の概念は組み込まれていると思うので,具体的な方法は調べてみてね.

#include <stdio.h>
#include <assert.h>
#include <math.h>
 
void sub(int i)
{
	switch(i)
	{
	case 0:
		printf("0\n");
		break;
	case 1:
		printf("1\n");
		break;
	default:
		assert(!"ここは実行されないはず");
	}
};
 
void print_sqrt(double i)
{
	assert(i>=0); // 0未満の値の平方根は虚数になるので禁止
	printf("%lf\n", sqrt(i));
};
 
int main(int argv, char* argc[])
{
	puts("実験1");
	sub(0);
	sub(1);
	//sub(2); // コメントをはずすと assertion error
 
	puts("実験2");
	print_sqrt(1);
	print_sqrt(3);
	print_sqrt(5);
	//print_sqrt(-1); // コメントをはずすと assertion error
 
	return 0;
}

sub 関数は引数として0か1を受け取り,受け取った値によって switch 文で分岐して,値を表示します.
0 か 1 以外の値を受け取ったときには default セクションが実行されます.覚えてますか?


print_sqrt 関数は引数として double 型の値を受け取り,それを標準ライブラリの sqrt 関数に渡して,結果を表示します.
なお, sqrt 関数は引数としてゼロ以上の値を与えたときしか正常に機能しません.
この辺りの仕様は,日本語訳がすぐに見つからなかったので英語版で(^^;


main 関数では,sub 関数と print_sqrt 関数を様々な引数で呼び出しています.
この2つの関数の呼び出しのひとつがコメントアウトされていますが,今は気にしないでね.


さて,見慣れた(?)単純なCプログラムですね(どうですか).
その中に見慣れないものがいくつかあると思います.
#include と assert(...); の2つです.
#include 文は,assert 命令を使うために必要なヘッダーファイルだということはすぐにわかるでしょう.
え? わからない? そういう人はもう一度勉強してきてね.
ともかく, assert を使うには assert.h をインクルードしないといけないんです.


では, assert とは何なのか?
これは,この命令を実行する時点で成立していなければならない条件を記述することで,成立していなかったときにエラーとして表面化させるものです.
assert(i>=0); // 0未満の値の平方根は虚数になるので禁止
という部分は,print_sqrt 関数に 0 未満の値が与えられたとき srqt 関数が正常に機能しなくなるので,エラーにならない条件を表明し,条件が成り立たないときはエラーとして表面化させています.
また,

assert(!"ここは実行されないはず");

という文は変則的な使い方ですが,文字列リテラルを整数値に変換すると正の数(=非ゼロ=真)になることを利用しています.
文字列リテラルの前に ! を付けることで常に偽となり,この assert が実行されるときには常にエラーとすることで,この部分が実行されることはないんだよ,と表明しているわけです.

それぞれ assertiton error になったときのエラー文を掲載します.
まずは,実験1の方のコメントをはずした場合.

bash-2.05b$ gcc assert.c
bash-2.05b$ ./a
実験1
0
1
assertion "!"ここは実行されないはず"" failed: file "assert.c", line 16
Aborted (core dumped)
bash-2.05b$

文字列リテラルがエラー文の中に表示され,エラーになったファイルがどれか,さらに何行目でエラーになったかまでわかります.
次に,実験2の方のコメントをはずした場合.

bash-2.05b$ gcc assert.c
bash-2.05b$ ./a
実験1
0
1
実験2
1.000000
1.732051
2.236068
assertion "i>=0" failed: file "assert.c", line 22
Aborted (core dumped)
bash-2.05b$

こちらも同じようにエラーになった条件とファイル名,その行番号がわかります.


このように assert を使うことで内在する論理的バグを表面化することができます.
何らかの条件の下でしか動かないコードを書くときには必ず入れておくようにしましょう.
(例えば,画面内をクリックしたときの座標を使用するときは X 座標も Y 座標もゼロ未満になることはないはずです)
そうすることで,突然のトラブルに遭遇する可能性を下げることができます.


長くなったので, VC++ の Debug/Release の使い分けはまた今度.
感想があれば嬉しいです.