INT_MAX (DRAFT)

概要

ε-N論法について - Qiita ε\text{-}N 論法を勉強するために読んでいたのだけれど、例として C のコードで表現されている部分が理解できなかったのでメモ。C 言語は現状入門サイトを読んだくらい…。

float a[ INT_MAX ] (Win10, VS2022)

まずこの時点で目玉が飛び出そうになる。ものすごく巨大な配列を宣言しているように見えるが…。

手元に Visual Stdio 2022 Community をインストールし、C++ コンソールプロジェクトを作成して書いてみる。

まず、INT_MAX が定義されていないのでエラーになった。C言語入門 - 整数型(char型 int型)の最大値と最小値 - limits.h - Webkaru を読むと INT_MAXlimits.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 PropertiesLinkerSystem と進む
  • 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 を見ると メモリの used7810 MiB まで上昇していた。INT_MAX * 4 = 8589934588 なので、おおよそ計算は合うことになる。

結論: Linux での float a[ INT_MAX ]

物理メモリが十分あって、ulimit -s unlimited していれば問題無し。元記事の筆者の方ももしかすると Linux 環境で試されていたのかもしれない。

休憩

float a[ INT_MAX ] を解釈するだけですごく大変だった。続きはまた今度。