🦔

なぜ400という値はint型になるのか

2021/07/24に公開約3,500字

C言語における整数定数について

タイトルがなんぞやという感じだと思うのですが、
C言語の整数定数という概念の仕様についての記事となります。
以下に3つほど例をあげます。

int a = 400;
printf("%d\n", 400);
long a = 400;

これら全ての式において400はint型として扱われます

最後に至ってはこいつは何を言っているんだと思うかもしれませんが、これから解説していきます。

整数定数とは

C言語において、整数定数(Integer constants)とは、小数点を含めない全ての数値のことです。
なので,123/012/0xff/-100これらの値は全て整数定数です。
整数の数値 = 整数定数といってよいでしょう。

この整数定数は一定のルールがあります。

その中で僕が面白いと思ったルールが暗黙的な型変換です。

暗黙的な型変換

数値には様々は型が存在します。
ex)int, unsigned int, long

これらの型は数値をコンパイルした時点で自動的に決定されます。
そしてその法則は定義されたルールに従いながら、その整数定数がとりうる値に自動的に型変換されます。
よくわからないので具体例をあげます。
先の例を使うと

int a = 400;

こちらの400という値があります。

暗黙的な型変換のルールとは整数定数は数値の大きさがある型の範囲に収まっているならば、その値の型は決定されるということです。

つまり、値400はint型の範囲に収まっているので、400の型はint型ということになります。

え、int a =としているからint型なのは当然なんじゃないの、という疑問があるかと思いますが、それは少し違います。
int aというのはaがint型であるということであり、400がint型ということではないです。
それは変数定義の型決定の順番に関係があります。
型決定の順番は以下のようになります。

  1. 右辺と左辺の型をそれぞれ決定する
  2. 左辺を右辺の型にキャストする

最初に両辺の型を決定してから、代入を行いその上で型決定をします。
なので先の例int a = 400;は以下のようになります。

  1. aの型をint, 400の型をintとする。
  2. 400をaの型, int型にキャストする。

400というint型の整数定数をaという変数に代入して、さらにintにキャストしています。
どちらもint型なので紛らわしいですね...

要するに、整数は変数の型とは別に、型を既に持っているということです。

もうすこし分かりやすいのがこちらです。

long a = 400;

先のルールに基づき

  1. aがlong型, 400がint型となります。
  2. aに代入され400がlong型にキャストされます。
    ただこれだと検証ができないですね。

ですので、以下のように代入ではない記載方法を使います。

printf("%d\n", 400);

こちらのプログラムは400という値が書かれているだけで型が指定されていません。
しかしint型として出力されます。
int型として出力されているかどうかを確認するためには以下を試してみると良いです。

printf("%ld\n", 400);

引数がintなので、コンパイルにてlong型での出力はできませんとwarningが出力されてしまいます。

これらのルールがいわゆる暗黙的な型変換と呼ばれるもので、整数値はその値に従って自動的に型が決定されて、コンパイルがされます。

面白い型変換の仕組み

整数定数の型決定は標準ドキュメントに従って、自動的に決定されます。
それがこちらです。(6.4.4.1#5参照)

Decimal Constant Octal or Hexadecimal Constant
int int
long int unsigned int
long long int long int
unsigned long int
long long int
unsigned long long int

Decimal Constantというのは10進数の数値です。
10進数の値の場合は、intの最大値を超えた数値はlong intに変換されます。
long intlongと同じです。

つまりINT_MAX + 1の値はlong型として処理されます。

printf("%ld\n", 2147483648);

こちらは問題なく出力されるので、long型に変換されていることが確認できます。

16進数の場合は型が異なる。

上記ルールのOctal or Hexadecimal Constant見る限り、
8進数/16進数の場合はint型の最大値を超えた場合の型が異なります。
具体的には、int → unsigned intとなります。
これは10進数での型変換のルールとは異なります。

16進数を用いて検証してみます。先程long型にキャストされた値と同じ値を使います。
2147483648を16進数に変換すると値は80000000です。
これを先程のprintfに代入します。

#include <stdio.h>

int main()
{
	printf("%ld\n", 0x80000000);
}

これをコンパイルすることにより、以下のようなwarningが出力されます。

test.c:5:18: warning: format specifies type 'long' but the argument has type 'unsigned int' [-Wformat]
        printf("%ld\n", 0x80000000);
                ~~~     ^~~~~~~~~~
                %u
1 warning generated.

コンパイルエラーから0x80000000unsigned intにキャストされていることがわかります。
これで(6.4.4.1#5)のルールが実装されている事がわかります。
ちなみにunsigned intをちょうど超える数4294967295 + 1を16進数に変換した値を代入しています。printfのフォーマット指定子をdに変更してみます。

#include <stdio.h>

int main()
{
	printf("%d\n", 0x100000000);
}
test.c:5:17: warning: format specifies type 'int' but the argument has type 'long' [-Wformat]
        printf("%d\n", 0x100000000);
                ~~     ^~~~~~~~~~~
                %ld
1 warning generated.

これによりlong型になっていることがわかりました。

その他

その他にも、接尾語としてU/Lなどを数値につけると型が指定できるなどの面白い仕様があるので、公式や日本語訳などを参照してみても面白いと思います!

参考

標準ドキュメント

http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1548.pdf
http://kikakurui.com/x3/X3010-2003-01.html
https://www.kijineko.co.jp/第5回-int型のサイズ/
http://www9.plala.or.jp/sgwr-t/c/sec13.html

Discussion

ログインするとコメントできます