13. ISSP, Univ. of Tokyo
13/51
コンパイラの警告を無視しない (2/4)
このプログラムの間違い、すぐにわかりますか?
#include <stdio.h>
int add_three (int verylongname){
int veryverylongname= veryverylongname+ 3;
return veryverylongname;
}
int main(void){
printf("%d?n", add_three(1));
}
add_threeは、引数に3を加えた値を返す関数のつもり
14. ISSP, Univ. of Tokyo
14/51
int add_three (int verylongname){
int veryverylongname= veryverylongname+ 3;
return veryverylongname;
}
int veryverylongname= verylongname+ 3;
ここは、本当はこれが正しい
int a = a + 1;
コンパイラはデフォルトで以下のコードに警告を出さない
コンパイラの警告を無視しない (3/4)
?本来なら引数であるべき変数を、似た名前のローカル変数で
書いてしまった
16. ISSP, Univ. of Tokyo
16/51
コンパイラの警告を無視しないのまとめ
普段から警告ゼロをキープすることが大事
警告が出たら
「あ、なんかやらかしたな」
と思うこと。
普段から「-Wall –Wextra」相当のオプションを指定する癖をつける
コンパイラの警告を無視しない
17. ISSP, Univ. of Tokyo
17/51
普段からassertをいれる癖をつける (1/4)
assertとは何か?
to state firmly that something is true
(From Longman Dictionary of Contemporary English)
C言語のassert
プログラムにおいて「成り立っていなければならない条件」を記述する
#include <assert.h>
...
assert(some condition);
中身が成り立っていれば何もしない
不成立なら、Assertion Failedと言ってプログラムがabortする
18. ISSP, Univ. of Tokyo
18/51
assertの例
void func(int a){
assert(a<10);
printf("%d?n",a);
}
int main(void){
func(8); //OK
func(11); //失敗する
}
入力となるaは10未満であるはず、
と宣言する
実行結果
$ ./a.out
8
Assertion failed: (a<10), function func, file test.cpp, line 5.
zsh: abort ./a.out
Assertionが破られたこと、
ソースのどこでAssertionが
破られたか教えてくれる
普段からassertをいれる癖をつける (2/4)
26. ISSP, Univ. of Tokyo
26/51
端の粒子の送り方
ナイーブな送り方
通信方法を減らした送り方
隣接するドメイン全てと通信を行う
3次元の場合、26回の通信が発生する
Domain A Domain B
Domain C
辺で接する領域からもらった粒子を、
別の方向で辺で接する領域へ転送
斜め方向の通信が必要なくなるため、
通信回数は6回で済む
sort+diff デバッグの例2:粒子情報送信(1/2)
39. ISSP, Univ. of Tokyo
39/51
問題の切り分け (2/2)
バグの発生箇所は、配列の領域外参照だった
const int N = 10;
double data[N];
???
double func(int index){
return data[index]; ← ここでindex=10だった
}
indexの値は0から9でないといけないのに、どこかでおかしな値が入った
(バグの発生箇所と、止まる箇所は一般に異なる)
おかしな値になった場所をどうやって探すか?
→ assertを入れまくる(if文でも可)
#include <assert.h>
double func(int index){
assert(index<N); assertには「満たすべき条件」を記載する
???
}
Assertion failed: (i<10), function func, file test.cc, line 7.
assertにひっかかると、以下のようなエラーが出て止まる
40. ISSP, Univ. of Tokyo
40/51
実際に経験したバグ (1/2)
double myrand_double (void){
return (double)(rand())/(double) (RAND_MAX);
}
int myrand_int (const int N){
return (int)(myrand_double()*N);
}
与えられた整数Nについて、0からN-1までの数字をランダムに返す関数を意図して
こんなコードを書いた
randは最高でRAND_MAXの値を返すので、
myrand_intは低確率(21億分の1の確率)でNを返す
実際には???
? ローカルPCで問題がなかったのに、スパコンでバグる
? スパコンでも条件によりバグったりバグらなかったりする
→ 当初、通信関連を疑ったが、乱数が原因だった
起きたこと
原因となった関数
RAND_MAX=2147483647
41. ISSP, Univ. of Tokyo
41/51
実際に経験したバグ (2/2)
const int N = 10;
double data[N];
int index = myrand_int(N);
// (ずっと遠くで)
return data[index];
この種のバグの原因に「最初から思い至る」のは難しい
? 確実にバグを再現するプログラムを保存しておく
? print文+assert文デバッグを行う
? 必ず原因を究明し、放置しない
21億分の1の確率でNを返す
21億分の1の確率で配列外参照
だいたい2000ノード、1日ジョブで確率50%くらいで失敗した
→ ローカルPCでは10年くらい流しても踏まないバグ
42. ISSP, Univ. of Tokyo
42/51
問題の切り分けとバージョン管理 (1/2)
機能を追加したらバグった?
→ その機能を追加したことによるバグ?
もともとバグっていたものが顕在化?
例:圧力測定ルーチンを追加したら、エネルギーが発散した
Observe
Pressure
Main
Kernel Ver. 1
Observe
Energy
Input A OK
Main
Kernel Ver. 2
Observe
Energy
Input B NG
圧力測定ルーチンのせいか?それともInput Bのせい(元々バグっていた)か?
→ ルーチン追加前のソースを取って来て、Input Bを食わせれば良い
Main
Kernel Ver. 1
Observe
Energy
Input B
OK?
NG?
バージョン管理をしていると、問題の切り分けが容易