INT_MAX (DRAFT)
概要
ε-N論法について - Qiita を 論法を勉強するために読んでいたのだけれど、例として C のコードで表現されている部分が理解できなかったのでメモ。C 言語は現状入門サイトを読んだくらい…。
float a[ INT_MAX ] (Win10, VS2022)
まずこの時点で目玉が飛び出そうになる。ものすごく巨大な配列を宣言しているように見えるが…。
手元に Visual Stdio 2022 Community をインストールし、C++ コンソールプロジェクトを作成して書いてみる。
まず、INT_MAX
が定義されていないのでエラーになった。C言語入門 - 整数型(char型 int型)の最大値と最小値 - limits.h - Webkaru を読むと INT_MAX
は limits.h
に定義されているようなので include する。
#include <stdio.h> #include <limits.h> int main(void) { float a[ INT_MAX ]; return 0; }
array is too large
のエラーが出る。無理やりビルドすると error C2148: total size of array must not exceed 0x7fffffff bytes
と出る。やっぱりちょっと無茶な配列確保をしているように見えるが…。
INT_MAX はいくつか
エラーを解釈するため、順番に確認してく。まず INT_MAX
がいくつなのか。
int main(void) { printf("INT_MAX = %d\n", INT_MAX); return 0; } // INT_MAX = 2147483647
2,147,483,647 (232 - 1) のようだ。これは 0x7fffffff に等しい。
sizeof_t(float) はいくつか
配列サイズが 型のサイズ
* 要素数
で決まるならば、次に float
のサイズが気になるところ。
int main(void) { printf("sizeof(float) = %d\n", sizeof(float)); return 0; } // sizeof(float) = 4
sizeof(float) = 4
らしい。
なお書式指定子に %d
を指定するのはダメなようで、下記のような警告が出ている。
Code | Description | |
---|---|---|
Warning | C6328 | Size mismatch: 'unsigned int64' passed as Param(2) when 'int' is required in call to 'printf'. |
Warning | C4477 | 'printf' : format string '%d' requires an argument of type 'int', but variadic argument 1 has type 'size_t' |
size_t 型を printf するための書式指定子は何か
いろいろググると Visual Studio C コンパイラが C の標準規格?にどこまで対応しているか?みたいな話が出てきてよくわからなくなってきたのだが、printfとsize_t型 - yohhoyの日記 を読むととりあえず %zd
のように指定すると良いようで、警告は消えた。
配列サイズが 0x7fffffff bytes 未満になるように宣言してみる
以上より、INT_MAX
= 0x7fffffff
であること、sizeof(float)
= 4
であることが分かった。
(INT_MAX / 4) * 4 = (2,147,483,647 / 4) * 4) = 2,147,483,644 = 0x7ffffffc < 0x7fffffff
だから、要素数を INT_MAX / 4
にすればエラーは出ないのではないか。
int main(void) { float a[ INT_MAX / sizeof(float) ]; return 0; } // fatal error C1126: automatic allocation exceeds 2G
error C2148: total size of array must not exceed 0x7fffffff bytes
は出なくなったが、代わりに fatal error C1126: automatic allocation exceeds 2G
が出た。
automatic allocation exceeds 2G エラーが出ないサイズを探す
INT_MAX / 4
= 536,870,911
から2分探索で減らしていきながら確かめた。
int main(void) { //--- fatal error C1126: automatic allocation exceeds 2G // float a[ INT_MAX / sizeof(float) ]; //--- fatal error C1126: automatic allocation exceeds 2G // float a[511999993]; //--- Stack overflow float a[511999992]; return 0; }
要素数 511,999,992
のときにコンパイルエラーは出なくなったが、今度は実行時に Stack overflow
が発生した。
Stack overflow が出ないサイズを探す
同じように2分探索で減らしていきながら確かめた。
int main(void) { //--- fatal error C1126: automatic allocation exceeds 2G // float a[ INT_MAX / sizeof(float) ]; //--- fatal error C1126: automatic allocation exceeds 2G // float a[511999993]; //--- Stack overflow // float a[511999992]; float a[ 257175 ]; //--- 明確な境界は無いが大体このあたり return 0; }
同じ値でも Stack overflow が起きるときと起きないときがあるものの、257,000
あたりから発生する様子。sizeof(float)
= 4
をかけると 1,028,000
となることから、大体 1MB あたりに上限がありそうだ。
Stack サイズを増やす
"visualc stacksize increase" とかでググっていたところ、/STACK (Stack allocations) | Microsoft Docs を見つけた。
The /STACK linker option sets the size of the stack in bytes. Use this option only when you build an .exe file.
The reserve value specifies the total stack allocation in virtual memory. For ARM64, x86, and x64 machines, the default stack size is 1 MB.
やはりスタックサイズはデフォルト 1MB らしい。そしてこの値は project's Property Pages
から変更できると書かれている。
- Visual Studio メニューバーから
Project
→<プロジェクト名> Properties
とクリックすると<プロジェクト名> Property Pages
が開く Configuration Properties
→Linker
→System
と進むStack Reseve Size
を大きな値にする。今回はINT_MAX
と同じ2147483647
としてみた
Stack サイズを増やしてから再実行
int main(void) { //--- fatal error C1126: automatic allocation exceeds 2G // float a[ INT_MAX / sizeof(float) ]; //--- fatal error C1126: automatic allocation exceeds 2G // float a[511999993]; //--- 成功 float a[511999992]; return 0; }
511999992
指定時はデフォルトでは Stack overflow となったが、Stack サイズを増やしたところ成功。511999993
のときは変わらず fatal error C1126: automatic allocation exceeds 2G
が出る。こちらは Stack サイズとはまた別の制限なのかもしれない。
結論: float 配列 をローカル宣言する場合のサイズ上限 (VS2022)
Windows + Visual Studio 2022 環境において、Stack Reseve Size
を十分大きく取った場合、float 配列は要素数 511,999,992 までならローカル変数として宣言できる。
float a[ INT_MAX ] (Ubuntu 20.04.3 LTS)
上記では Visual Studion 2022 上でゴリゴリ調べていたが、Linux だとどうなのだろう?と思い立った。
$ cat ipsd.c #include <limits.h> #include <stdio.h> int main(void) { float a[ 2096110 ]; return 0; } $ gcc ipsd.c && ./a.out $ gcc ipsd.c && ./a.out Segmentation fault (core dumped) $ gcc ipsd.c && ./a.out Segmentation fault (core dumped) $ gcc ipsd.c && ./a.out Segmentation fault (core dumped) $ $ gcc ipsd.c && ./a.out $ gcc ipsd.c && ./a.out Segmentation fault (core dumped) $ gcc ipsd.c && ./a.out $ gcc ipsd.c && ./a.out $ gcc ipsd.c && ./a.out
やはり安定はしないが、要素数 2096110
あたりが上限に見える。VS2022 に比べると大きく見える。
Linux のスタックサイズ上限
Linux Stack Sizes - Stack Overflow を見ると Linux のスタックサイズ上限は ulimit -s
で確認/設定できるとのこと。自身の環境も 8192
だった。
core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 31289 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 31289 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited
スタックサイズを増やしてから再実行
$ ulimit -s unlimited $ $ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 31289 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) unlimited cpu time (seconds, -t) unlimited max user processes (-u) 31289 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited $ $ cat ipsd.c #include <limits.h> #include <stdio.h> int main(void) { float a[ INT_MAX ]; return 0; } $ gcc ipsd.c && ./a.out $ time ./a.out real 0m9.900s user 0m0.380s sys 0m1.829s
ということで、float a[ INT_MAX ]
でも全く問題なく実行できた。
ちなみに実行環境は ESXi 上に建てた Ubuntu VM で、割り当てメモリは 8G。上記実行中に top
を見ると メモリの used
が 7810 MiB
まで上昇していた。INT_MAX * 4
= 8589934588
なので、おおよそ計算は合うことになる。
結論: Linux での float a[ INT_MAX ]
物理メモリが十分あって、ulimit -s unlimited
していれば問題無し。元記事の筆者の方ももしかすると Linux 環境で試されていたのかもしれない。
休憩
float a[ INT_MAX ]
を解釈するだけですごく大変だった。続きはまた今度。