web-dev-qa-db-ja.com

calloc()は合計でSIZE_MAX以上を割り当てることができますか?

最近のコードレビュー では、

一部のシステムでは、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;
}
_
29
Toby Speight

_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とは異なる値を返すことはまったく問題ありません。割り当てられたメモリを賢明な方法で実際に使用する方法、またはヒープ上のそのような大きなメモリチャンクをサポートするシステムも、別の話です。

16
Lundin

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ビットでした。

20
chux

ちょっとした追加:ちょっとした計算で、SIZE_MAX * SIZE_MAX = 1(Cルールに従って評価した場合)を示すことができます。

ただし、calloc(SIZE_MAX、SIZE_MAX)は、SIZE_MAXバイトのSIZE_MAX要素の配列へのポインターを返す、OR NULLを返す、次の2つのことのいずれかを行うことのみが許可されます。引数を乗算して1の結果を取得し、1バイトを割り当てることで合計サイズを0にクリアします。

2
gnasher729

標準のテキストによると、おそらく、標準はこの種のものについて(意図的に言う人もいます)曖昧だからです。

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実装(組み込みなどに関連する場合があります)との互換性を明確にしようとしない限り、このような悪い実装を考慮する必要があるというコードレビューの主張は偽りです。

2
R..

から

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バイトに制限する必要がある理由がわかりません。

2
Swordfish

標準では、_ptr+number1+number2_が有効なポインターになる可能性があるが、_number1+number2_が_SIZE_MAX_を超えるように、何らかの方法でポインターを作成できるかどうかについては何も述べていません。それは確かに_number1+number2_が_PTRDIFF_MAX_を超える可能性を考慮します(何らかの理由でC11は16ビットのアドレス空間を持つ実装でさえ32ビット_ptrdiff_t_を使用する必要があると決定しました) 。

標準では、実装がそのような大きなオブジェクトへのポインタを作成する手段を提供することを義務付けていません。ただし、関数calloc()を定義します。その説明は、そのようなオブジェクトを作成するように要求できることを示唆し、次の場合にcalloc()がnullポインタを返すことを提案します。オブジェクトを作成できません。

ただし、あらゆる種類のオブジェクトを便利に割り当てる機能は、実装品質の問題です。標準では、特定の割り当て要求が成功することを要求することはなく、実装が使用不能になる可能性のあるポインターを返すことを禁止することもありません(一部のLinux環境では、malloc()は、アドレス空間;物理ストレージが不足しているときにポインターを使用しようとすると、致命的なトラップが発生する可能性があります)。 xyの数値積がSIZE_MAXを超える場合、ポインタを生成するよりも、calloc(x,y)の気まぐれな実装がnullを返す方が確かに良いでしょう。そのバイト数にアクセスするために使用することはできません。ただし、yバイトのxオブジェクトにアクセスするために使用できるポインターを返すかどうかは、nullを返すよりも良いか悪いかを考慮する必要があります。各動作は、ある状況では有利であり、別の状況では不利です。

0
supercat