C/C++の定数の型の話, C90/C99の差分のびみょーな話
Cのソースコードに m = 195; とか n = 0xffffffff; とか書いたときの定数(右辺)の型って、なんであるかご存じでしょうか? また、C90(1990年版のISO C言語規格)とC99(1999年版のそれ)ではその型が微妙に異なったりすることがあるんですが、ご存じでしょうか? さらには、お使いのマシンがILP32であるかLP64であるかLLP64であるかによっても、微妙に型が違ってきたりするんですが、それについてはどうでしょうか? えーもちろん、普段は「Uがついてなかったらint, Uがついてたらunsigned intジャネーノ?」くらいの理解でも殆ど不自由しないわけですが、詳細な理解がないとハマるケースも稀にあります。
私はというと、上に書いたような事は、C90/99の差違を除いてはだいたい理解しているつもりだったのですが、C90/99の差異について無頓着だったがために、先日ちょっとした不意打ちをくらいました。事例(クイズ?)として紹介してみます。
クイズ
このようなコードを考えます。妙なコードですが、問題の起こる最小の例を抜き出したということでご勘弁ください。これは、C90とC99で異なる振る舞いをするコードになっています。4294967295は、2進数で書くと1111 1111 1111 1111 1111 1111 1111 1111です(1が32個並ぶ)です。
#include <stdio.h> void foo(int nume) { unsigned long long x = nume / 4294967295; printf("%llu\n", x); } int main() { foo(-1); return 0; }
これを、次の4つの環境でコンパイル・リンクして実行すると、
- (1) ILP32, C90準拠コンパイラ (gccなど)
- (2) ILP32, C99準拠コンパイラ (gcc4 -std=c99 など)
- (3) LP64, C90準拠コンパイラ (gccなど)
- (4) LP64, C99準拠コンパイラ (gcc4 -std=c99 など)
それぞれ、どのような出力が得られるでしょうか?
えー、4294967295 の型が(1)-(4)でそれぞれ何になるかがポイントです。それによって除算がどう行われるかが、除算の結果の型も含めて決まります。変数xへの代入部分は、この例では答えに影響しません。というか、影響しないようにxの型を選んであります。
答え
(1)-(4)の出力は順に
- (1) 1
- (2) 0
- (3) 0
- (4) 0
となります。次の通り:
% gcc -v gcc version 4.1.1 20070105 (Red Hat 4.1.1-51) % uname -m x86_64 % gcc -m32 div.c && ./a.out 1 % gcc -m32 -std=c99 div.c && ./a.out 0 % gcc -m64 div.c && ./a.out 0 % gcc -m64 -std=c99 div.c && ./a.out 0
順を追って解説します。まず、「定数の型がどのように決まるか」から押さえましょう。
定数の型はどのように決まるのか (C90編)
C90における定数の型は、ISO C90 規格書*1の6.1.3.2に載ってます。
整数定数の型は、次の並びのうちでその値を表現できる最初の型とする。
- 接尾語無しの10進数: int, long int, unsigned long int
- 接尾語無しの8進数又は16進数: int, unsigned int, long int, unsigned long int
- 文字u又はUが接尾語として付く場合: unsigned int, unsigned long int
- 文字l又はLが接尾語として付く場合: long int, unsigned long int
- 文字u又はU及び文字l又はLが接尾語として付く場合: unsigned long int
たとえば、ILP32環境で定数「1」や「214783647」*2はintですが、「2147483648」*3はintでもlong intでも表現できないのでunsigned long intになります。型が、符号有無も含めて異なります。また、同じ数(!?)でも10進定数にするか16進定数にするかで型が異なることがあります。LP64環境で「2147483648」は long int ですが、「0x80000000」は unsigned int になってしまいます。やはり型が、符号有無も含めて異なります。ぅゎ。
定数の型はどのように決まるのか (C99編)
C99における定数の型は、ISO C99 規格書の6.4.4.1に載ってます。C99の場合は表になっているんですが、私の方でC90にフォーマットを合わせたものを記します。ひとまず「接尾語無しの10進数」のlong intの次が、C90ではunsigned long intだったのに対しC99ではlong long intになっています。これが今回のポイントです。
整数定数の型は、次の並びのうちでその値を表現できる最初の型とする。
- 接尾語無しの10進数: int, long int, long long int
- 接尾語無しの8進数又は16進数: int, unsigned int, long int, unsigned long int, long long int, unsigned long long int
- 文字u又はUが接尾語として付く場合: unsigned int, unsigned long int, unsigned long long int
- 文字l又はLが接尾語として付く場合の10進数: long int, long long int
- 文字l又はLが接尾語として付く場合の8進数又は16進数: long int, unsigned long int, long long int, unsigned long long int
- 文字u又はU及び文字l又はLが接尾語として付く場合: unsigned long int, unsigned long long int
- 文字ll又はLLが接尾語として付く場合の10進数: long long int
- 文字ll又はLLが接尾語として付く場合の8進数又は16進数: long long int, unsigned long long int
- 文字u又はU及び文字ll又はLLが接尾語として付く場合: unsigned long long int
ILP32+C99では定数「1」や「214783647」はintですが、「2147483648」は long long int になります。またC90同様、同じ数(?)でも10進定数にするか16進定数にするかで型が異なることがあります。ILP32+C99で「2147483648」は long long int ですが、「0x80000000」は unsigned int です。やはり型が、符号有無も含めて異なります。
解説
これを踏まえ、(1)-(4)で4294967295の型がどうなるかを起点として除算の動きを見ます。
(1)では、4294967295の型はunsigned long int になります。ですから、除算は
-1 [int] / 4294967295 [unsigned long int] ==(usual arithmetic conversion)==> -1 [unsigned long int] / 4294967295 [unsigned long int] ==> 4294967295 [unsigned long int] / 4294967295 [unsigned long int] ==> 1
のように行われ(適当な記法でスミマセン)、結果は1になります。0にはなりません!。結果の型は unsigned long int です。
(2)では、4294967295の型はlong long intになります。ですから、除算は
-1 [int] / 4294967295 [long long int] ==(usual arithmetic conversion)==> -1 [long long int] / 4294967295 [long long int] ==> 0
のように行われ、結果は0になります。結果の型は long long int です。64bitの符号「付き」の型に揃えられてから演算されるので、(1)のように -1 が 4294967295UL に読み替えられないのがポイントでしょう。
(3)と(4)はlong型が64bitである関係で、同じ演算になります。C90/99で違いは出ません。4294967295の型はlong intになります。除算は
-1 [int] / 4294967295 [long int] ==(usual arithmetic conversion)==> -1 [long int] / 4294967295 [long int] ==> 0
のように行われ、結果は0になります。結果の型は long int です。-1が-1のまま演算される点は、(2)と同じですね。
(余談) usual arithmetic conversion
usual arithmetic conversion (通常の算術型変換) については、Cの規格を参照してください。このpdfにも少し載ってます。long long型(64bit型)のことを忘れてもいいなら、いやあまりよくないとおもいますが、K&Rの第二版にも一応載ってます。
GCCの警告
実はGCCは、C90とC99で型が異なるような定数を使用すると、C90モードでコンパイルした際に次のような警告を出してくれます。この警告が出たら、無視せずに原因をよく考える方がよさそうです。
warning: this decimal constant is unsigned only in ISO C90
しかしながら、データモデルによって型が変化するケース、10進で書くか8/16進で書くかによって型が変化するケースについては、プログラマが気を付けるしかなさそうです。
まとめ
C言語のソースコード中に記載した定数は、結構意外な型として扱われていることがあり、その詳細を知らないと(たまに)問題が起こる。特に、
- 10進定数を8/16進定数に書き直すと(あるいはその逆)、型が変化してしまうことがあるので注意
- 同じことですが、ある数を10進定数として書くか、8/16進定数で書くかによって型が異なってしまうことがあるので注意
- C90準拠のコンパイラとC99準拠のコンパイラで、定数の型が異なってしまうことがあるので注意
- ILP32環境でコンパイルするかLP64環境でコンパイルするかで、定数の型が異なってしまうことがあるので注意
この3点に注意が必要です。また、
というのもよい習慣だと思います。
C++について
C++言語(ISO/IEC 14882:2003)は、2003年版の規格では、C言語互換部分についてはC90を参照してますので、gccをC90モードで使ったときと同じ結果になります。
% cp div.c div.cpp % g++ -m32 div.cpp && ./a.out div.cpp:4: 警告: this decimal constant is unsigned only in ISO C90 1 % g++ -m64 div.cpp && ./a.out 0
*1:C90のJIS版は、旧規格になりますのでWebから発注はできません。http://www.webstore.jsa.or.jp/webstore/Com/html/jp/ShoppingInfo.htm のFAX注文書で JIS X3010:1993 と X3010:1996(Amd1) を発注することになります。合計で3.5万円程です。詳しくはJSAに問い合わせを
*2:2進で 0111 1111 1111 1111 1111 1111 1111 1111 です
*3:2進で 1000 0000 0000 0000 0000 0000 0000 0000 です