Entries

スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

リテラル整数の型は何か

タグ: C++ gcc
#define HOGE 0x80000000

この時 HOGE は

  • int型の -2147483648
  • unsigned int型の 2147483648

のどちらになるか。

『JISX3014:2003』は、整数リテラルの型を以下の様に定めている。

整数接尾語のつかない8進リテラル又は16進リテラルの場合、その型は、int型、unsignd int型、long int型、unsigned long int型の順に見ていって、その値が表現可能な最初の型とする。

ではこれを踏まえ、VS2005を使って検証してみよう。

int main(int argc, char* argv[])
{
	char sample[] = {
		0x8000000, // int範囲
		0x80000000, // int範囲(2の補数)
		0x800000000, // 標準整数型範囲外
		2147483647, // int範囲
		2147483648, // int範囲外(正数)
		-2147483649, // int範囲外(負数)
		21474836480, // 標準整数型範囲外
		-21474836480, // 標準整数型範囲外
	};

	return 0;
}

VS2005のコンパイラは、型変換によってオーバーフローする可能性のある箇所を見つけると、変換元の型付きでwarningを出してくれる。
なので、これをコンパイルしてwarningを見れば、リテラルの型が分かる。

(4) : warning C4305: '初期化中' : 'int' から 'char' へ切り詰めます。
(4) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(5) : warning C4305: '初期化中' : 'unsigned int' から 'char' へ切り詰めます。
(5) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(6) : warning C4305: '初期化中' : '__int64' から 'char' へ切り詰めます。
(6) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(7) : warning C4305: '初期化中' : 'int' から 'char' へ切り詰めます。
(7) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(8) : warning C4305: '初期化中' : 'unsigned long' から 'char' へ切り詰めます。
(8) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(9) : warning C4146: 符号付きの値を代入する変数は、符号付き型にキャストしなければなりません。
(9) : warning C4305: '初期化中' : 'unsigned long' から 'char' へ切り詰めます。
(9) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(10) : warning C4305: '初期化中' : '__int64' から 'char' へ切り詰めます。
(10) : warning C4309: '初期化中' : 定数値が切り捨てられました。
(11) : warning C4305: '初期化中' : '__int64' から 'char' へ切り詰めます。
(11) : warning C4309: '初期化中' : 定数値が切り捨てられました。

前述の HOGE に相当するのは5行目なので、正解は「unsigned int」。
なぜint型じゃないかと言えば、簡単な話、C++は負数を2の補数で表現することを規定していない(最上位ビット0で負数を表現することも許されている)から。
だから例え2の補数表現で見ればint型の負数であったとしても、符号付きの型にすることはできない。
よって、 HOGE をint型に変換した場合の動作は、符号付き整数型への型変換でオーバーフローが発生するため処理系依存ということになる。

さて、せっかくなので他のパターンも見てみよう。

6行目のリテラルは__int64型(long long型)となっているが、これはC++的には規格外の動作。
gccでも同じように動作するし、『JISX3010:2003』(C99)ではlong long int型になることが規定されているが、少なくとも『JISX3014:2003』はこのパターンに触れていない。

10行目以降も同様、C++的には「未定義」の動作。

だがこの中で特に注意すべきなのは9行目。

(9) : warning C4146: 符号付きの値を代入する変数は、符号付き型にキャストしなければなりません。

検証コードのコメントには「代入されそうな値が負数」だから負数と書いているが、実はここのリテラルは「-2147483649」ではなく「2147483649」。
その前に付いている「-」は符号ではなく(整数リテラルに符号は使えない)、単項マイナス演算子なのである。

これがどういった事態を引き起こすか、もう一つコードを書いて検証してみよう。

#include <stdio.h>

int main(int argc, char* argv[])
{
	long long sample[] = {
		0x8000000000000000LL, // 64bit整数最小数(2の補数)
		-2147483648, // 32bit整数範囲の負数のつもり(0x80000000)
		-4294967295, // 64bit整数範囲の負数のつもり(-0xffffffff)
		-4294967295LL, // 64bit整数範囲の負数(型を明示)
		-4294967296LL, // 64bit整数範囲の負数(-0x100000000)
		9223372036854775808ULL, // 64bit整数範囲外(正数)
		-9223372036854775809LL, // 64bit整数範囲外(負数)
	};

	for(int i = 0; i < 7; i++)
	{
		printf("%lld\n", sample[i]);
	}

	return 0;
}

これはVS2005、g++ 3.2.3、gcc 3.2.3(-std=c99)のいずれでコンパイルした場合も、以下の結果を返す。

-9223372036854775808
2147483648
1
-4294967295
-4294967296
-9223372036854775808
9223372036854775807

見ての通り、代入先が64bit整数型であるにも関わらず、先にunsinged int型で計算されてしまうため、「-4294967295」は「1」となって代入されてしまう。
また特に気をつけなければならないのは、MSDNのC4146の項にも書かれている「-2147483648」。
見た目的には完全にlong int型なのに、64bit整数型に代入すると「2147483648」になってしまう。

ただし、gcc 4.0.1はちょっと違っていて、 g++ でコンパイルした場合は他のコンパイラ同様の結果を返すが、 gcc -std=c99 でコンパイルした場合、2行目と3行目の出力は以下のようになる。

-2147483648
-4294967295

これはC99ではこの2つのリテラルは「unsigned int型」ではなく「long long型」になることが決まっているからであり、C99準拠のコンパイラとしてはこれが正しい動作なのである。

スポンサーサイト

コメント

コメントの投稿

コメントの投稿
管理者にだけ表示を許可する

トラックバック

トラックバック URL
http://idlysphere.blog66.fc2.com/tb.php/81-5b66fde8
この記事にトラックバックする(FC2ブログユーザー)

Appendix

タグ

Blog内検索

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。