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


んじゃ、解説するよ。何のことかわからない人は[前の記事](http://qiita.com/raccy/items/419594556442bcf9d1df)を読んでね。

## C言語の仕様を把握せよ！

手元に[教科書](http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=57853)は置いているかな。なに、高いって？まぁ、俺も持ってないけど。仕方がないので、[教科書の草書](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf)を見てくれ。

さて、なぜ前の記事のようになったかであるが、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]

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

### 10進数の整数リテラルは勝手に昇格する

C言語の整数リテラルは`int`型だ。末尾に`L`や`LL`、`U`などを付けると`long`型や`unsigend int`型などの別の型になる。という説明だけでは不十分である。実際はLを付けなくても`long`型とかになる場合がある。

どういうことかというと、10進数の整数リテラルは`int`型の範囲を超える場合は、自動的に昇格してより大きい型として扱われる。

昇格の順番: `int` -> `long` -> `long long`

`long long`でも表せないほど大きな整数は文法エラーになる。[^2]

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

### 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 = 2<sup>31</sup>-1 = 2147483647
* INT_MIN = -2<sup>31</sup> = -2147483648
* UINT_MAX = 2<sup>32</sup>-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をお持ちの方はお試しあれ。

```terminal
clang -Xclang -ast-dump -fsyntax-only -std=c11 num_literal.c
```