web-dev-qa-db-ja.com

C ++は、動的配列のサイズを教えません。しかし、なぜ?

C++には、次のような動的に作成された配列のサイズを取得する方法がないことを知っています。

int* a;
a = new int[n];

私が知りたいのは:なぜですか? C++の仕様でこれを忘れただけですか、それとも技術的な理由がありますか?

情報はどこかに保存されていませんか?結局のところ、コマンド

delete[] a;

どのくらいのメモリを解放する必要があるかを知っているようですので、delete[]には、aのサイズを知る何らかの方法があります。

61
jarauh

多くの場合、メモリマネージャは特定の倍数(たとえば64バイト)の領域のみを割り当てることがわかります。

したがって、新しいint [4]、つまり16バイトを要求できますが、メモリマネージャーはリクエストに64バイトを割り当てます。このメモリを解放するために、要求されたメモリ量を知る必要はありません。64バイトの1ブロックを割り当てただけです。

次の質問は、要求されたサイズを保存できませんか?これは追加のオーバーヘッドであり、誰もが支払う準備ができているわけではありません。たとえば、Arduino Unoには2kのRAMしかありません。そのコンテキストでは、割り当てごとに4バイトが突然大きくなります。

その機能が必要な場合は、std :: vector(または同等のもの)を使用するか、より高レベルの言語を使用します。 C/C++は、使用することを選択するのと同じくらいのオーバーヘッドで作業できるように設計されています。これは1つの例です。

2
DewiW

「不要なものにお金を払わない」という基本的なルールの続きです。あなたの例ではdelete[] a;does n't intにはデストラクタがないため、配列のサイズを知る必要があります。あなたが書いた場合:

std::string* a;
a = new std::string[n];
...
delete [] a;

次に、deleteはデストラクタを呼び出す必要があります(そして、呼び出す数を知る必要があります)。その場合、newはそのカウントを保存する必要があります。ただし、すべての場合に保存する必要がないneedではないため、Bjarneはアクセスを許可しないことにしました。

(後知恵では、これは間違いだったと思う...)

もちろんintであっても、somethingは割り当てられたメモリのサイズを知る必要がありますが、:

  • 多くのアロケーターは、アライメントと利便性の理由から、サイズを便利な倍数(64バイトなど)に切り上げます。アロケーターは、ブロックの長さが64バイトであることを知っていますが、それはnが1 ...または16であったためかどうかを知りません。

  • C++ランタイムライブラリは、割り当てられたブロックのサイズにアクセスできない場合があります。たとえば、newdeleteが内部でmallocfreeを使用している場合、C++ライブラリにはブロックのサイズを知る方法がありません。 mallocによって返されます。 (通常、newmallocは両方とも同じライブラリの一部ですが、常にではありません。)

38
Martin Bonner

基本的な理由の1つは、Tの動的に割り当てられた配列の最初の要素へのポインターと他のTへのポインターに違いがないことです。

ポインターが指す要素の数を返す架空の関数を考えます。
「サイズ」と呼びましょう。

本当にいいですね。

すべてのポインターが等しく作成されるという事実がなかった場合:

char* p = new char[10];
size_t ps = size(p+1);  // What?

char a[10] = {0};
size_t as = size(a);     // Hmm...
size_t bs = size(a + 1); // Wut?

char i = 0;
size_t is = size(&i);  // OK?

最初の9、2番目の10、3番目の9、最後の1であると主張することができますが、これを実現するには「サイズすべての単一オブジェクト.
A charは、64ビットマシンで128ビットのストレージを必要とします(アライメントのため)。これは必要な数の16倍です。
(上記の10文字の配列aには、少なくとも168バイトが必要です。)

これは便利かもしれませんが、許容できないほど高価です。

もちろん、引数が実際にデフォルトのoperator newによる動的割り当ての最初の要素へのポインターである場合にのみ、明確に定義されたバージョンを想定することもできますが、これは考えられるほど有用ではありません。

14
molbdnilo

あなたは、システムの一部がサイズについて何かを知る必要があることは正しいです。しかし、その情報を取得することはおそらくメモリ管理システムのAPIでカバーされていません(malloc/freeを考えてください)。アップ。

4
Carsten S

私が found という形式のoperator deleteをオーバーロードするという奇妙なケースがあります:

void operator delete[](void *p, size_t size);

パラメーター サイズ デフォルトでは、void * pが指すメモリブロックのサイズ(バイト単位)になります。これが当てはまる場合は、少なくともoperator newの呼び出しによって渡された値があり、したがって、単に sizeof(タイプ) 配列に格納されている要素の数を配信します。

あなたの質問の「なぜ」部分に関しては、 Martin の「あなたが必要としないものにお金を払わない」というルールが最も論理的なようです。

2
Jean Louw

その配列をどのように使用するかを知る方法はありません。割り当てサイズは必ずしも要素番号と一致しないため、割り当てサイズを使用することはできません(使用可能な場合でも)。

これは、C++ではない他の言語の深刻な欠陥です。 std :: vectorで必要な機能を実現しながら、配列への生のアクセスを保持します。実際に何らかの作業を行わなければならないコードでは、生のアクセスを保持することが重要です。

多くの場合、配列のサブセットに対して操作を実行し、言語に追加のブックキーピングが組み込まれている場合、reallocateサブ配列を作成し、データをコピーしてAPIで操作しますマネージドアレイが必要です。

データ要素をソートするという些細なケースを考えてみてください。管理配列がある場合、データをコピーせずに再帰を使用して新しいサブ配列を作成し、再帰的に渡すことはできません。

別の例は、2x2の「蝶」で始まるデータを再帰的に操作し、配列全体に戻る方法で動作するFFTです。

マネージドアレイを修正するには、この欠陥を修正するために「何か他のもの」が必要になり、その「何か他のもの」は「イテレータ」と呼ばれます。 (マネージ配列が作成されましたが、90%以上の反復子が必要なため、ほとんど関数に渡されません。)

1
Quazil