web-dev-qa-db-ja.com

配列の最大サイズが「大きすぎる」のはなぜですか?

this answer 、そのsize_tは、指定されたシステムの可能な最大のタイプを保持するのに十分な大きさであることが、標準によって常に保証されています。

ただし、このコードはgcc/Mingwでのコンパイルに失敗します。

#include <stdint.h>
#include <stddef.h>

typedef uint8_t array_t [SIZE_MAX];

エラー:配列 'array_t'のサイズが大きすぎます

ここで標準の何かを誤解していますか? size_tは、特定の実装に対して大きすぎることを許可されていますか?または、これはMingwの別のバグですか?


編集:さらなる研究はそれを示しています

typedef uint8_t array_t [SIZE_MAX/2];   // does compile
typedef uint8_t array_t [SIZE_MAX/2+1]; // does not compile

これはたまたま同じ

#include <limits.h>

typedef uint8_t array_t [LLONG_MAX];           // does compile
typedef uint8_t array_t [LLONG_MAX+(size_t)1]; // does not compile

ですから、符号付き整数型に基づいて最大許容サイズを設定しても意味がないため、これはMingwのバグであると信じています。

54
Lundin

SIZE_MAX/2の制限は、実装上のsize_tおよびptrdiff_tの定義に基づいています。これらの定義では、ptrdiff_tとsize_tのタイプが同じ幅を持つように選択されています。

C標準の義務1 その型size_tは符号なしで、型ptrdiff_tは符号付きです。

2つのポインターの差の結果は、常に2 タイプptrdiff_tがあります。つまり、実装では、オブジェクトのサイズをPTRDIFF_MAXに制限する必要があります。そうしないと、2つのポインターの有効な差をptrdiff_t型で表現できず、未定義の動作が発生します。

したがって、値SIZE_MAX/2は値PTRDIFF_MAXと等しくなります。実装がオブジェクトの最大サイズをSIZE_MAXにすることを選択した場合、ptrdiff_t型の幅を増やす必要があります。ただし、オブジェクトの最大サイズをSIZE_MAX/2に制限する方がはるかに簡単です。その後、ptrdiff_t型のサイズを、size_t型よりも大きいか等しい正の範囲にする必要があります。

標準はこれらを提供します3 コメント4 話題になっている。


(ISO/IEC 9899:201xから引用)

1 (7.19共通定義2)
タイプは
ptrdiff_t
これは、2つのポインターを減算した結果の符号付き整数型です。
size_t
これは、sizeof演算子の結果の符号なし整数型です。

2 (6.5.6加算演算子9)
2つのポインターを引くと、両方が同じ配列オブジェクトの要素、または配列オブジェクトの最後の要素の1つを指します。結果は、2つの配列要素の添え字の差です。結果のサイズは実装定義であり、その型(符号付き整数型)はヘッダーで定義されたptrdiff_tです。結果がそのタイプのオブジェクトで表現できない場合、動作は未定義です。

3 (K.3.4整数型3)
非常に大きなオブジェクトサイズは、多くの場合、オブジェクトのサイズが誤って計算されたことを示しています。たとえば、負の数値は、size_tなどの符号なしの型に変換されると、非常に大きな正の数値として表示されます。また、一部の実装では、size_t型で表現できる最大値のオブジェクトをサポートしていません。

4 (K.3.4整数型4)
これらの理由から、プログラミングエラーを検出するためにオブジェクトサイズの範囲を制限することが有益な場合があります。大きなアドレス空間を持つマシンを対象とする実装の場合、RSIZE_MAXは、サポートされる最大オブジェクトのサイズまたは(SIZE_MAX >> 1)の小さい方として定義することをお勧めします。大きなオブジェクト。小さいアドレス空間を持つマシンをターゲットとする実装では、RSIZE_MAXをSIZE_MAXとして定義することができます。これは、実行時制約違反と見なされるオブジェクトサイズがないことを意味します。

62
2501

size_tの範囲は、実装でサポートされる最大のオブジェクトのサイズを格納するのに十分であることが保証されています。その逆は当てはまりません。サイズがsize_tの範囲全体を満たすオブジェクトを作成できるとは限りません。

このような状況では、質問は次のとおりです。SIZE_MAXは何を表していますか?サポートされているオブジェクトの最大サイズは?またはsize_tで表現可能な最大値?答えは、後者です。つまり、SIZE_MAX(size_t) -1です。 SIZE_MAXバイトの大きさのオブジェクトを作成できる保証はありません。

その背後にある理由は、size_tに加えて、実装がptrdiff_tを提供する必要があるためです。タイプptrdiff_tは署名されているため、実装には次の選択肢があります。

  1. サイズSIZE_MAXの配列オブジェクトを許可し、ptrdiff_tよりもsize_twideにします。少なくとも1ビット広くする必要があります。このようなptrdiff_tは、サイズSIZE_MAX以下の配列を指す2つのポインターの差に対応できます。

  2. サイズSIZE_MAXの配列オブジェクトを許可し、ptrdiff_tと同じ幅size_tを使用します。ポインターがSIZE_MAX / 2要素よりも離れている場合、ポインターの減算がoverflowを引き起こし、未定義の動作を引き起こすという事実を受け入れます。言語仕様は、このアプローチを禁止していません。

  3. ptrdiff_tと同じ幅のsize_tおよびrestrictSIZE_MAX / 2による最大配列オブジェクトサイズを使用します。このようなptrdiff_tは、サイズSIZE_MAX / 2以下の配列を指す2つのポインターの差に対応できます。

単純に、3番目のアプローチに従うことにした実装を扱っています。

17
AnT

これは、実装固有の動作に非常によく似ています。

ここでMac OSを実行しています。gcc6.3.0で定義をコンパイルできる最大サイズはSIZE_MAX/2; SIZE_MAX/2 + 1もうコンパイルしません。

一方、witch clang 4.0.0の最大のものはSIZE_MAX/8、およびSIZE_MAX/8 + 1ブレーク。

5
avysk

ゼロから推論するだけで、size_tは、あらゆるオブジェクトのサイズを保持できるタイプです。オブジェクトのサイズは、アドレスバスの幅によって制限されます(たとえば、32ビットコードと64ビットコードを処理できる多重化とシステムを無視し、「コード幅」と呼びます)。最大の整数値であるMAX_INTに類似しており、SIZE_MAXsize_tの最大値です。したがって、サイズSIZE_MAXのオブジェクトはすべてアドレス可能なメモリです。実装がエラーとしてフラグを立てることは理にかなっていますが、実際のオブジェクトがスタックまたはグローバルメモリに割り当てられている場合にのみエラーであることに同意します。 (その量のmallocへの呼び出しはいずれにしても失敗します)

0
Paul Ogilvie