VisualStudio 2019 (cl.exe) のバグっぽい挙動

# これは何？

cl.exe で遊んでいたらバグっぽい挙動に遭遇したので記す。

# ソースコード

わかりやすくするとこんな感じ。

```c:c
#include <stdio.h>

int main(void){
  typedef char ary[1u<<30];
  typedef struct m1{ ary a; } m1;
  typedef struct m2{ ary a, b; } m2;
  typedef struct m3{ ary a, b, c; } m3;
  typedef struct m4{ ary a, b, c, d; } m4;
  typedef struct m5{ ary a, b, c, d, e; } m5;
  printf( "sizeof(m1)=%zu\n", sizeof(m1));
  printf( "sizeof(m2)=%zu\n", sizeof(m2));
  printf( "sizeof(m3)=%zu\n", sizeof(m3));
  printf( "sizeof(m4)=%zu\n", sizeof(m4));
  printf( "sizeof(m5)=%zu\n", sizeof(m5));
  return 0;
}
```

内容としては、むちゃくちゃ巨大な構造体を用意して、それの `sizeof` を出すだけ。
まともなら、

```text
sizeof(m1)=1073741824
sizeof(m2)=2147483648
sizeof(m3)=3221225472
sizeof(m4)=4294967296
sizeof(m5)=5368709120
```

と出るか、構造体のサイズが大きすぎるという理由でエラーになるか、どっちか。


# x64 tool (VS2019)

まずは、VisualStudio 2019 の win64 native tool で。

```text
>cl /Wall main.c && main
Microsoft(R) C/C++ Optimizing Compiler Version 19.29.30140 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

main.c
Microsoft (R) Incremental Linker Version 14.29.30140.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
sizeof(m1)=1073741824
sizeof(m2)=2147483648
sizeof(m3)=3221225472
sizeof(m4)=4
sizeof(m5)=1073741824
```

`sizeof(m3)` まではまともだけど、 `sizeof(m4)` が `4` という意外な展開。
`0` ならまだわかる（おかしいけど）。 `4` ってなんだろう？ ……と初出時は書いていたけど、たぶんこの 4 は、空っぽの構造体のサイズだ。試しにメンバのない構造体をつくってみると、C言語ではエラー（知らなかった）。C++ としてコンパイルしたら、メンバのない構造体も `sizeof(m4)` も 1 になった。

`sizeof(m5)` は `sizeof(m1)`  と同じで `0x40000000` になる。

いずれにせよ、 `sizeof(m4)` と `sizeof(m5)` は正しくない。

# x86 tool (VS2019)

```text
>cl /Wall main.c && main
Microsoft(R) C/C++ Optimizing Compiler Version 19.29.30140 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

main.c
Microsoft (R) Incremental Linker Version 14.29.30140.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:main.exe
main.obj
sizeof(m1)=1073741824
sizeof(m2)=2147483648
sizeof(m3)=3221225472
sizeof(m4)=4
sizeof(m5)=1073741824
```

x64 と同じ結果。
ていうか。
4GB を超えるサイズの構造体なんだから、定義した時点でエラー、せめて警告は出してくれ、と思った。

# 本当にサイズを間違えている。

本当にサイズを間違えているので、x86 環境で、下記のように m4 を ローカル変数として 4 個ぐらい確保しても警告もエラーも出ない。

```c:c
extern void foo(char *p, char *p1, char *p2, char *p3);

int main(void){
  typedef char ary[1u<<30];
  typedef struct m1{ ary a; } m1;
  typedef struct m4{ ary a, b, c, d; } m4;
  m4 a, b, c, d; // m1 だと「fatal error C1126: 自動メモリ割り当てが 2G を超えました。」
  foo(a.a, b.a, c.a, d.a);
  return 0;
}
```

このように明らかにメモリ空間に入り切らない量のローカル変数をスタックにおいても

```
>cl /Wall /utf-8 /c usem.c
Microsoft(R) C/C++ Optimizing Compiler Version 19.29.30140 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

usem.c
```

警告一つでない。おそろしい。

コメントに書いたとおり、 `m4` ではなく `m1` を 4個確保するとエラーになる。正しい。

# 動くようにする

例えば以下のようにすると、x86 でも x64 でも実行可能となる。

```c:c
#include <stdio.h>
#include <stdlib.h>

int main(void){
  typedef char ary[1u<<30];
  typedef struct m{
    ary a, b, c, d, e;
  } m;
  m * p = calloc(sizeof(m),1); // 遅そう...
  if (!p) {
    puts( "p is zero" );
    return 1;
  }
  puts( "OK, p is NOT zero" );
  p->a[0] = 11;
  p->e[1] = 22;
  printf( "a[0], a[1] is %d, %d\n", p->a[0], p->a[1] );
  printf( "e[0], e[1] is %d, %d\n", p->e[0], p->e[1] );
  return 0;
}
```

以下に x64 での実行例を置いたが、 x86 でも同様の結果となった。

```text
>cl /Wall /utf-8 run.c && run
Microsoft(R) C/C++ Optimizing Compiler Version 19.29.30140 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

run.c
Microsoft (R) Incremental Linker Version 14.29.30140.0
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:run.exe
run.obj
OK, p is NOT zero
a[0], a[1] is 11, 22
e[0], e[1] is 11, 22
```

恐ろしい。

もちろん MacBook Pro 上の clang なんかで同じコードを動かすと正しく

```text
$ clang -Wall main.c && ./a.out
OK, p is NOT zero
a[0], a[1] is 11, 0
e[0], e[1] is 0, 22
```

となる。

# 感想

まあ、こんな巨大な構造体を定義することは無いので困ることはないけど、バグなんだと思う。
VisualStudio も最近バグ少ないよなぁと思っていたんだけど、まだ色々あるんだろうなと思った。