結論
タイトルがすべてを物語っています。long int 型のサイズは環境によって 4byte, 8byte の場合が考えられるという話です。
事の発端
SNSでこんな投稿を発見。
int a = LONG_MAX;
a と等しいものは?
INT_MAX
INT_MAX - 1
0
INT_MIN
これを見た私の脳内フロー
- これは静的型付言語で、
LONG_MAX
は定数だろうからC言語だろう - 定数
LONG_MAX
は long int 型 - long int 型のサイズは 8byte だろう(本当か?)
-
int a = LONG_MAX;
の代入で暗黙の型変換(long int -> int)が行われる - int 型のサイズは 4byte であり、long int 型のサイズより小さい(本当か?)。
LONG_MAX
= 2^63-1(本当か?)は 4byte では表現できないから、この暗黙の型変換は処理系依存では?
型変換に関しての詳細:初心者のためのポイント学習C言語, 少し詳しい型変換の説明
実験
環境
- OS : windows10 x64
- CPU : Intel Core i7-7500U
- コンパイラ : MinGW
gcc
- コンパイルオプション :
-g -Wall
実験1
# include <stdio.h>
# include <string.h>
# include <limits.h>
int main(void){
int a[2];
int b[2];
memset(a, 0, 2 * sizeof(int));
memset(b, 0, 2 * sizeof(int));
long int max = LONG_MAX;
// a[0] = max と同じ、暗黙の型変換あり
*a = max;
// 代入先の型情報を弄って型変換なし
*((long int*)b) = max;
printf("sizeof(long int) = %d\n", sizeof(long int));
printf("LONG_MAX = %ld(0x%lx)\n",max,max);
printf("a[0] = %d(0x%x)\n", a[0], a[0]);
printf("a[1] = %d(0x%x)\n", a[1], a[1]);
printf("b[0] = %d(0x%x)\n", b[0], b[0]);
printf("b[1] = %d(0x%x)\n", b[1], b[1]);
return 0;
}
実行結果
sizeof(long int) = 4
LONG_MAX = 2147483647(0x7fffffff)
a[0] = 2147483647(0x7fffffff)
a[1] = 0(0x0)
b[0] = 2147483647(0x7fffffff)
b[1] = 0(0x0)
環境によって、**long int 型は 4byte だった。**64bit 処理系では 8byte の場合が多いが、windowsは例外的に 4byte らしい。型変換も何もないではないか!
MaryCoew, 【C言語/C++】データ型のサイズ・範囲の一覧【32bit/64bit環境】
実験2
なら long long int
型ならどうか。
# include <stdio.h>
# include <string.h>
# include <limits.h>
int main(void){
int a[2];
int b[2];
int c[2];
int d[2];
memset(a, 0, 2 * sizeof(int));
memset(b, 0, 2 * sizeof(int));
memset(c, 0, 2 * sizeof(int));
memset(d, 0, 2 * sizeof(int));
long long int max = LLONG_MAX;
// 暗黙の型変換あり
*a = max;
// 型変換なし
*((long long int*)b) = max;
// 暗黙の型変換の追加実験
long long int test = 0x789abcdef0123456;
*c = test;
long long int* _d = (long long int*)d;
*_d = test;
printf("sizeof(long long int) = %d\n", sizeof(long long));
printf("LLONG_MAX = %lld(0x%llx)\n",max,max);
printf("a[0] = %d(0x%x)\n", a[0], a[0]);
printf("a[1] = %d(0x%x)\n", a[1], a[1]);
printf("b[0] = %d(0x%x)\n", b[0], b[0]);
printf("b[1] = %d(0x%x)\n", b[1], b[1]);
printf("test = %lld(0x%llx)\n", test,test);
printf("c[0] = %d(0x%x)\n", c[0], c[0]);
printf("c[1] = %d(0x%x)\n", c[1], c[1]);
printf("d[0] = %d(0x%x)\n", d[0], d[0]);
printf("d[1] = %d(0x%x)\n", d[1], d[1]);
}
実行結果
-Wall
オプションを付けてコンパイルすると、unknown conversion type character 'l' in format
と警告がでますが無視です。
sizeof(long long int) = 8
LLONG_MAX = 9223372036854775807(0x7fffffffffffffff)
a[0] = -1(0xffffffff)
a[1] = 0(0x0)
b[0] = -1(0xffffffff)
b[1] = 2147483647(0x7fffffff)
test = 8690466096661279830(0x789abcdef0123456)
c[0] = -267242410(0xf0123456)
c[1] = 0(0x0)
d[0] = -267242410(0xf0123456)
d[1] = 2023406814(0x789abcde)
この結果を説明するにはエンディアン(バイトオーダー)の話が必要です。詳しくは、[バイトオーダー]ビックエンディアン/リトルエンディアン
a,c
の暗黙の型変換がある場合、最下位 32bit がそのままコピーされている模様。実験の処理系は Little Endian であるため、サイズの小さな型に表現できない大きなサイズの値を代入するときメモリを順番に入るだけコピーしていると推定できます。
一方で、b,d
の型変換がない場合は明白。Little Endian では最下位ビットから順に 1byte ずつメモリ上に並んでいるからです。