[C言語] long int は 8byte と思い込んではいけない話

# 結論
タイトルがすべてを物語っています。long int 型のサイズは環境によって 4byte, 8byte の場合が考えられるという話です。

# 事の発端
SNSでこんな投稿を発見。

> `int a = LONG_MAX;`
>  a と等しいものは？
>  1. `INT_MAX`
>  2. `INT_MAX - 1`
>  3. `0`
>  4. `INT_MIN`

これを見た私の脳内フロー

1. これは静的型付言語で、`LONG_MAX` は定数だろうからC言語だろう
2. 定数 `LONG_MAX` は long int 型
3. long int 型のサイズは 8byte だろう（本当か？）
4. `int a = LONG_MAX;`の代入で暗黙の型変換（long int -> int）が行われる
5. int 型のサイズは 4byte であり、long int 型のサイズより小さい（本当か？）。`LONG_MAX` = 2^63-1（本当か？）は 4byte では表現できないから、この暗黙の型変換は処理系依存では？  

型変換に関しての詳細：[初心者のためのポイント学習Ｃ言語, 少し詳しい型変換の説明](http://www9.plala.or.jp/sgwr-t/detail/TypeConversion.html)

# 実験

## 環境
- OS : windows10 x64
- CPU : Intel Core i7-7500U
- コンパイラ : MinGW `gcc`
- コンパイルオプション : `-g -Wall`

## 実験１
```c:long_int.c
#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環境】](https://marycore.jp/prog/c-lang/data-type-ranges-and-bit-byte-sizes/) 

## 実験２
なら `long long int`型ならどうか。

```c:long_long_int.c
#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)
```
この結果を説明するにはエンディアン（バイトオーダー）の話が必要です。詳しくは、[[バイトオーダー]ビックエンディアン/リトルエンディアン](https://qiita.com/ryuichi1208/items/31442f9e8a7a7c94aeec)  

`a,c`の暗黙の型変換がある場合、最下位 32bit がそのままコピーされている模様。実験の処理系は Little Endian であるため、サイズの小さな型に表現できない大きなサイズの値を代入するときメモリを順番に入るだけコピーしていると推定できます。  
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440643/5e2b212c-20a2-da03-8019-5873cf3b958d.png" width=60%>

一方で、`b,d`の型変換がない場合は明白。Little Endian では最下位ビットから順に 1byte ずつメモリ上に並んでいるからです。
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440643/21302b0a-211d-958e-64c6-477e473467c2.png" width=60%>