勉強会での宿題とか疑問

 先日、C/C++セキュアコーディングハーフデイキャンプに参加してきた。そのときの宿題と疑問点についてまとめようと思う。

  1. 算術型変換

 まず宿題から。CやC++は四則演算などの計算を行う際に型を自動的に変換する。int型とdouble型の足し算をしたらint型はdouble型へ自動的に変換され、結果はdouble型として出てくるといった感じ。多分どんなC言語の入門書でも書いてあることだと思う。


 で、勉強会の時に unsigned int と long int の計算ではどうなるのか、という部分でかなり議論になった。そのときの候補は unsigned long int と long int。
 算術型変換のルールには

「符号付き整数型を持つオペランドの型が、符号なし整数型をもつオペランドの型の全ての値を表現できるならば、符号なし整数型をもつオペランドを、符号付き整数型を持つオペランドの型に変換する」

というものがあり、普通に読むとunsigned int と long int は多くの環境で32bitなので、unsigned int の全ての値を long int で表すことはできない。つまり変換後の値は long int にはならないはずである。

 ところが、上記の説明文の『値』という部分は実はbit幅のことではいか? という話が出てきた。もし、『全ての値』がbit的に見て表現できていればよいということならば、long int であっているということになる。
 結局講師の方もはっきりしたことは分からないということだったので宿題扱いとなった。


 正直、『値』がbit幅なわけがないと思っているのだが、一応原文を参照してみた。

"if one oerand is long int and the other is unsigned long int, the effect depends on
whether a long int chan represent all values of an unsigned int; if so the
unsigned int operand is converted to long int; if not, both are converted to
unsigned long int."

 同じようなことが書かれている。翻訳の際に間違って〜なんてことはなさそうだ。それならやっぱりunsigned long int が正解だと思うのだが・・・。


 プログラムで試してみようと思っても型変換で unsigned long int になっているか long int になっているか調べる方法なんてあっただろうか? あったら試してみたい。

  1. 最適化した場合のassert()とif()

 次は疑問に思ったこと。勉強会の最後のほうで質問にでたことなのだが、

#include <assert.h>
#include <limits.h>

int foo(int a) {
 assert(a + 100 > 1);
 printf("%d %d\n", a + 100, a);
 return a;
}

int main(void) {
 foo(100);
 foo(INT_MAX);
 return 0;
}

 このようなプログラムを行ったとき、assert(a + 100 > 1)の部分は整数オーバーフローを起こす可能性のある部分であり、処理系によっては最適化でこの一文を未定義の動作として完全に無視するものもあるらしい。


 そして上記のプログラムはgcc4.21で-O2オプションをつけてコンパイルするとassert文が削除されるらしい。そこまでは分かるが、この最適化によってif文までもが削除されるというのである。
 正直、if文という処理に大きく関わる部分が削除されるとは到底考えられないのだが、絶対ないとも言い切れないので試してみた。


 上記のassertの部分をif文にして、printf文にて通ったかどうかを出力するようにプログラムを変更してみた。

#include <assert.h>
#include <limits.h>

int foo(int a) {
 if(a + 100 > 1) printf("if --> ");
 printf("%d %d\n", a + 100, a);
 return a;
}

int main(void) {
 foo(100);
 foo(INT_MAX);
 return 0;
}


 そして自分の環境(gcc 2.95.3)で何の最適化オプションもつけずに実行。一つ目の"if --> "は出力されて、二つ目は出力されない。予想通りの結果である。
 ところが、この後-Oオプションをつけた場合でも-O2オプションをつけた場合でも二つ目は出力されなかった(assertにしても全て停止。バージョンが違うとこうも違うのか)。


 実行環境が1つだけでは心もとないのでcodepadをつかって同じようにプログラムを実行してみた。ただし、codepadではgcc 4.1.2を使用し最適化オプションは-Oで固定になっている。
 実行結果を見てみると・・・二つ目の"if --> "が出力されている!(assertの場合も試してみたが、止まらずに実行された)


 どうやら処理系のバージョンにもよるようだが、最適化によって整数オーバーフローを起こす未定義動作の部分はassertもifも削除される可能性があるようだ。