最近のコードレビュー では、
一部のシステムでは、
calloc()
は合計バイト数_SIZE_MAX
_を超えて割り当てることができますが、malloc()
は制限されています。
私の主張は、calloc()
がオブジェクトの配列のためのスペースを作成するので、それは間違っているということです。これは、配列であり、それ自体がオブジェクトです。また、オブジェクトのサイズを_SIZE_MAX
_より大きくすることはできません。
だから私たちのどちらが正しいのでしょうか? _size_t
_の範囲より大きいアドレス空間を持つ(おそらく仮想の)システムでは、calloc()
は、積が_SIZE_MAX
_より大きい引数で呼び出されたときに成功することができますか?
もっと具体的に言うと、次のプログラムはゼロ以外のステータスで終了しますか?
_#include <stdint.h>
#include <stdlib.h>
int main()
{
return calloc(SIZE_MAX, 2) != NULL;
}
_
_SIZE_MAX
_はオブジェクトの最大サイズを指定する必要はありませんが、_size_t
_の最大値を指定する必要がありますが、これは必ずしも同じではありません。 なぜ配列の最大サイズが「大きすぎる」のですか? 、
しかし、明らかに、_SIZE_MAX
_よりも大きい値を_size_t
_パラメーターを必要とする関数に渡すことは明確に定義されていません。したがって、理論的には_SIZE_MAX
_が制限であり、理論的にはcalloc
は_SIZE_MAX * SIZE_MAX
_バイトの割り当てを許可します。
malloc
/calloc
の利点は、タイプなしでオブジェクトを割り当てることです。タイプを持つオブジェクトには、_SIZE_MAX
_のような特定の制限より大きくならないなどの制限があります。ただし、これらの関数の結果が指すデータには型がありません。 (まだ)配列ではありません。
正式には、データには宣言されたタイプはありませんが、割り当てられたデータ内に何かを格納すると、の効果が得られますストレージに使用されるデータアクセスのタイプ(C17 6.5§6)。
これは、割り当てられたものには(まだ)型がないため、calloc
がCのどの型でも保持できるよりも多くのメモリを割り当てることができることを意味します。
したがって、C標準に関する限り、calloc(SIZE_MAX, 2)
がNULLとは異なる値を返すことはまったく問題ありません。割り当てられたメモリを賢明な方法で実際に使用する方法、またはヒープ上のそのような大きなメモリチャンクをサポートするシステムも、別の話です。
Calloc()は合計でSIZE_MAX以上を割り当てることができますか?
「特定のシステムでは、calloc()
は合計__SIZE_MAX
_を超えるバイト数を割り当てることができますが、malloc()
は制限されます。」 comment から投稿しました。その根拠を説明します。
size_t
_size_t
_は少なくとも16ビットのunsigned型です。
_
size_t
_は、sizeof
演算子の結果の符号なし整数型です。 C11dr§7.192「その実装定義の値は、以下に示す対応する値よりも大きいか、またはそれよりも大きくなければならない」... _
size_t
_ _SIZE_MAX
_の制限... 65535§7.20.32
sizeof
sizeof
演算子は、そのオペランドのサイズ(バイト単位)を生成します。これは、式または括弧付きの型の名前です。 §6.5.3.42
calloc
_void *calloc(size_t nmemb, size_t size);
_
calloc
関数は、それぞれnmemb
がサイズであるsize
オブジェクトの配列にスペースを割り当てます。 §7.22.3.22
_nmemb * size
_が_SIZE_MAX
_を十分に超える状況を考えます。
_size_t alot = SIZE_MAX/2;
double *p = calloc(alot, sizeof *p); // assume `double` is 8 bytes.
_
calloc()
が本当に_nmemb * size
_バイトを割り当て、_p != NULL
_がtrueの場合、これはどの仕様に違反しましたか?
各要素(各オブジェクト)のサイズは表現可能です。
_// Nicely reports the size of a pointer and an element.
printf("sizeof p:%zu, sizeof *p:%zu\n", sizeof p, sizeof *p);
_
各要素にアクセスできます。
_// Nicely reports the value of an `element` and the address of the element
for (size_t i = 0; i<alot; i++) {
printf("value a[%zu]:%g, address:%p\n", i, p[i], (void*) &p[i]);
}
_
calloc()
詳細
「nmemb
オブジェクトの配列のためのスペース」:これは確かに競合の重要なポイントです。 「配列にスペースを割り当てる」には<= _SIZE_MAX
_が必要ですか? C仕様にはこの制限を必要とするものは何もないので、結論を出します。
calloc()
は、合計で_SIZE_MAX
_より多くを割り当てることができます。
確かにuncommonは、calloc()
が大きな引数を使用して非NULL
-準拠かどうかを返します。通常、このような割り当ては使用可能なメモリを超えているため、問題は議論の余地があります。私が遭遇した唯一のケースは、 巨大なメモリモデル で、_size_t
_は16ビットで、オブジェクトポインターは32ビットでした。
ちょっとした追加:ちょっとした計算で、SIZE_MAX * SIZE_MAX = 1(Cルールに従って評価した場合)を示すことができます。
ただし、calloc(SIZE_MAX、SIZE_MAX)は、SIZE_MAXバイトのSIZE_MAX要素の配列へのポインターを返す、OR NULLを返す、次の2つのことのいずれかを行うことのみが許可されます。引数を乗算して1の結果を取得し、1バイトを割り当てることで合計サイズを0にクリアします。
標準のテキストによると、おそらく、標準はこの種のものについて(意図的に言う人もいます)曖昧だからです。
6.5.3.4あたり¶2:
sizeof
演算子は、オペランドのサイズ(バイト単位)を返します
および7.19¶2ごと:
size_t
これは、
sizeof
演算子の結果の符号なし整数型です。
実装がサイズがsize_t
で表現できないタイプ(配列タイプを含む)を許可する場合、前者は一般に満足できません。 「配列」を指すcalloc
によって返されたポインターに関するテキストを解釈するかどうかに関係なく、オブジェクトには常に配列が含まれます:unsigned char[sizeof object]
型のオーバーレイ配列その表現。
せいぜい、SIZE_MAX
(またはその他の理由でPTRDIFF_MAX
)より大きいオブジェクトの作成を許可する実装には、致命的に悪いQoI(実装品質)の問題があります。特定の壊れたC実装(組み込みなどに関連する場合があります)との互換性を明確にしようとしない限り、このような悪い実装を考慮する必要があるというコードレビューの主張は偽りです。
から
7.22.3.2 calloc関数
あらすじ
1#include <stdlib.h> void *calloc(size_t nmemb, size_t size);`
説明
2 calloc関数は、サイズがそれぞれsizeであるnmembオブジェクトの配列にスペースを割り当てます。スペースはすべてのビット0に初期化されます。返品
3 calloc関数は、nullポインターまたは割り当てられたスペースへのポインターを返します。
割り当てられたスペースをSIZE_MAX
バイトに制限する必要がある理由がわかりません。
標準では、_ptr+number1+number2
_が有効なポインターになる可能性があるが、_number1+number2
_が_SIZE_MAX
_を超えるように、何らかの方法でポインターを作成できるかどうかについては何も述べていません。それは確かに_number1+number2
_が_PTRDIFF_MAX
_を超える可能性を考慮します(何らかの理由でC11は16ビットのアドレス空間を持つ実装でさえ32ビット_ptrdiff_t
_を使用する必要があると決定しました) 。
標準では、実装がそのような大きなオブジェクトへのポインタを作成する手段を提供することを義務付けていません。ただし、関数calloc()
を定義します。その説明は、そのようなオブジェクトを作成するように要求できることを示唆し、次の場合にcalloc()
がnullポインタを返すことを提案します。オブジェクトを作成できません。
ただし、あらゆる種類のオブジェクトを便利に割り当てる機能は、実装品質の問題です。標準では、特定の割り当て要求が成功することを要求することはなく、実装が使用不能になる可能性のあるポインターを返すことを禁止することもありません(一部のLinux環境では、malloc()は、アドレス空間;物理ストレージが不足しているときにポインターを使用しようとすると、致命的なトラップが発生する可能性があります)。 x
とy
の数値積がSIZE_MAXを超える場合、ポインタを生成するよりも、calloc(x,y)
の気まぐれな実装がnullを返す方が確かに良いでしょう。そのバイト数にアクセスするために使用することはできません。ただし、y
バイトのx
オブジェクトにアクセスするために使用できるポインターを返すかどうかは、null
を返すよりも良いか悪いかを考慮する必要があります。各動作は、ある状況では有利であり、別の状況では不利です。