LoginSignup
31
31

More than 5 years have passed since last update.

C言語の整数リテラルの謎【解説編】

Posted at

んじゃ、解説するよ。何のことかわからない人は前の記事を読んでね。

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型だ。末尾にLLLUなどを付けると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))という解釈もできる。2147483648INT_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

  1. 整数リテラルの場合で、浮動小数点数リテラルの場合はまた別だ。 

  2. Clangではエラーにならず、警告表示でコンパイルは通るが、C言語としては正しくない。 

31
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
31