んじゃ、解説するよ。何のことかわからない人は前の記事を読んでね。
C言語の仕様を把握せよ!
手元に教科書は置いているかな。なに、高いって?まぁ、俺も持ってないけど。仕方がないので、教科書の草書を見てくれ。
さて、なぜ前の記事のようになったかであるが、C言語の仕様に基づいて動作した結果であるとしか言いようがない。ただし、動作には環境依存の部分もある。前の記事はMac OS X(64bit)上のClangでの結果だ。この環境はLP64(int:32/long:64/void*:64)なので、それにあわせて説明する。ILP32(32bitの環境)やLLP64(64bitのVC++)とは少しだけ動作が異なるので、注意して欲しい(ただ、現象としては普通のPCなら同じになるはず)。では、今回の動作に関係がある仕様を見ていこう。
-
はリテラルではなく単項演算子
一つ目の注意点は-
である。-1
と書いたとき、リテラルは1
のみで-
は単項演算子である。つまり、-(1)
と書いてある場合と全く同じ解釈がされる。これって以外と知らない人が多いんじゃないかなと思ったりする。他のプログラミング言語(PythonやRubyなど)でも-
は単項演算子扱いで、リテラルとはしないとする場合が多いと思っているのだが。1
10進数の整数リテラルは勝手に昇格する
C言語の整数リテラルはint
型だ。末尾にL
やLL
、U
などを付けるとlong
型やunsigend int
型などの別の型になる。という説明だけでは不十分である。実際はLを付けなくてもlong
型とかになる場合がある。
どういうことかというと、10進数の整数リテラルはint
型の範囲を超える場合は、自動的に昇格してより大きい型として扱われる。
昇格の順番: int
-> long
-> long long
long long
でも表せないほど大きな整数は文法エラーになる。2
8進数/16進数の整数リテラルは符号無しを挟みながら勝手に昇格する
先ほどの話は10進数の場合だ。8進数と16進数では少しだけ異なる。通常はint
であること、自動的に昇格して大きい型として扱われることは同じなのだが、昇格していく対象に符号無し型が含まれる。つまり、下記のような順番で昇格していく。
昇格の順番: int
-> unsigned int
-> long
-> unsigned long
-> long long
-> unsigned long long
では実際の処理を見ていこう。
環境依存であると述べたので、まずは環境を見よう。Mac OS XのClangでは次のようになっている。
-
char
のサイズは8bits(つまり、1Byte=8bits) -
int
のサイズは4Bytes(=32bits) -
long
のサイズは8Bytes(=64bits) - 整数値の負の表現は2の補数
- INT_MAX = 231-1 = 2147483647
- INT_MIN = -231 = -2147483648
- UINT_MAX = 232-1 = 4294967295
sizeof(-2147483648)
について
まずは、こっちから見ていく。
-
は単項演算子なので、sizeof(-2147483648)
はsizeof(-(2147483648))
という解釈もできる。2147483648
はINT_MAX
を越えているので、int
型では表現できない。よって、10進数の整数による自動昇格がおこりlong
型になる。単項演算子-
はint
型以上の型に対して型の昇格がおきないので、-(2147483648)
もlong
型になる。long
は8Bytesなので、sizeof(-(2147483648))
は8
となる。
考えてみたら、当たり前のことだね。
(-2147483648 == -0x80000000)
について
さてさて、つぎはこっちだ。
同じように、(-2147483648 == -0x80000000)
は((-(2147483648)) == (-(0x80000000)))
と解釈できる。まずはこの-(2147483648)
だが、さきほどlong
型になっていると述べた。では実際のメモリはどうなっているのだろうか?(本当はリトルエンディアンだけど、わかりやすくするため、ビッグエンディアンっぽく表記している。番地が逆順なのに注意。)
[番地] 07 06 05 04 03 02 01 00
2147483648 -> 00 00 00 00 80 00 00 00 # long型
-(2147483648) -> FF FF FF FF 80 00 00 00 # long型
次に0x80000000
なのだが、これは16進数なので、int
に入らない場合は、unsigned int
に入るかどうかを確認する。INT_MAX
より大きくUINT_MAX
以下なので、unsigned int
型として解釈される。そして単項演算子-
はint
型以上の型に対して型の昇格がおきないので、-(0x80000000)
もunsigned int
型である。
[番地] 03 02 01 00
0x80000000 -> 80 00 00 00 # unsigned int型
-(0x80000000) -> 80 00 00 00 # unsigned int型
この後==
演算子があるが、型が異なるので昇格が起きる。long
型の方が大きいので、-(0x80000000)
もlong
型に昇格する。符号無しからの昇格なので、単に0を追加していくだけになる。
[番地] 07 06 05 04 03 02 01 00
(long)(-(0x80000000)) -> 00 00 00 00 80 00 00 00 # long型
これは-(2147483648)
と異なるので、結果が0
になるのである。
他のはなぜうまくいくの?
他の演算がうまくいくのは、int
型として切り詰められたり、同じ符号有りから符号有りへの昇格なので問題なかったりするためだ。なお、INT_MIN
はマクロなのだが、ちゃんとint
型になるように(-2147483647-1)
といった形で定義されていたりする。
以上だけど、わかったかな?まぁ、俺の解釈もあやしいかもしれないけどね。符号有り/無しは混ぜるな危険、最大値や最小値はちゃんとマクロ使いましょうって事。
本当かどうか確かめたい!
最後に、実際どんな型になっているかは、下記コマンドを使うとどう解釈していっているかがわかったりする。Clangをお持ちの方はお試しあれ。
clang -Xclang -ast-dump -fsyntax-only -std=c11 num_literal.c