8

静的配列よりも malloc を使用する利点は何ですか?(失敗時に NULL を返す以外に)? 次のプログラムは、ループがコメント解除されている場合にのみ、すべての RAM を使い果たし、スワップの充填を開始します。クラッシュしません。

...

#include <stdio.h>

unsigned int bigint[ 1u << 29 - 1 ];
unsigned char bigchar[ 1u << 31 - 1 ];

int main (int argc, char **argv) {
  int i;
/*   for (i = 0; i < 1u << 29 - 1; i++) bigint[i] = i; */
/*   for (i = 0; i < 1u << 31 - 1; i++) bigchar[i] = i & 0xFF; */

  getchar();
  return 0;
}

...

いくつかの試行錯誤の後、上記が GCC 4.3 を搭載した 32 ビット Intel マシンで許可されている最大の静的配列であることがわかりました。これは標準の制限ですか、コンパイラの制限ですか、それともマシンの制限ですか? どうやら、私はそれらを好きなだけ持つことができます。それはセグメンテーション違反になりますが、とにかくmallocが私に与えるよりも多くを求めた(そして使用しようとした)場合に限ります。

静的配列が実際に割り当てられ、安全に使用できるかどうかを判断する方法はありますか?

編集: 仮想メモリ システムにヒープを処理させるのではなく、malloc を使用してヒープを管理する理由に興味があります。どうやら、配列のサイズを必要と思われるサイズの何倍にもすることができ、仮想メモリシステムは必要なものだけをRAMに保持します。たとえば、これらの巨大な配列の末尾 (または先頭) に書き込みを行わない場合、プログラムは物理メモリを使用しません。さらに、すべての場所に書き込むことができる場合、ヒープ内のポインターをインクリメントしたり、同じプロセスで以前の割り当てを検索したりする以外に、malloc は何をしますか?

編集者注: int が 32 ビットの場合1 << 31 、未定義の動作が発生1uするため、質問を read に変更しました。質問の意図は、大きな静的バッファの割り当てについて尋ねることです。

4

8 に答える 8

16

ええと、実際には2つの理由があります:

  1. 一部のシステムは仮想メモリ管理を行わないため、移植性のためです。

  2. 必然的に、この配列を便利にするために小さなチャンクに分割する必要があります。次に、すべてのチャンクを追跡し、最終的に、不要になった配列のチャンクの一部を「解放」し始めると、ヒットしますメモリの断片化の問題。

全体として、多くのメモリ管理機能を実装することになります (実際にはほとんど malloc を再実装します) 移植性の利点はありません。

したがって、理由は次のとおりです。

  • メモリ管理のカプセル化と標準化によるコードの移植性。

  • コードの再利用による個人の生産性の向上。

于 2009-05-08T12:11:19.197 に答える
11

malloc を使用すると、配列を拡大および縮小できます。配列は動的になるため、必要なものを正確に割り当てることができます。

于 2009-05-08T10:56:41.817 に答える
7

これは、カスタム メモリ管理と呼ばれるものだと思います。それはできますが、そのメモリのチャンクを自分で管理する必要があります。このチャンクを使いこなす独自の malloc() を作成することになります。

于 2009-05-08T10:54:28.890 に答える
3

それにかんする:

いくつかの試行錯誤の後、上記が GCC 4.3 を搭載した 32 ビット Intel マシンで許可されている最大の静的配列であることがわかりました。これは標準の制限ですか、コンパイラの制限ですか、それともマシンの制限ですか?

1 つの上限は、4 GB (32 ビット) の仮想アドレス空間がユーザー空間とカーネル空間の間でどのように分割されているかによって異なります。Linux の場合、最も一般的なパーティショニング スキームは、ユーザー空間用に 3 GB の範囲のアドレスを持ち、カーネル空間用に 1 GB の範囲のアドレスを持つと思います。パーティショニングはカーネルのビルド時に設定可能で、2GB/2GB および 1GB/3GB 分割も使用されています。実行可能ファイルがロードされると、オブジェクトをバックアップするために実メモリが割り当てられているかどうかに関係なく、すべてのオブジェクトに仮想アドレス空間を割り当てる必要があります。

于 2009-05-08T12:01:01.280 に答える
3

その巨大な配列を 1 つのコンテキストに割り当てることはできますが、他のコンテキストには割り当てることができない場合があります。たとえば、配列が構造体のメンバーであり、その構造体を渡したい場合です。一部の環境では、構造体のサイズが 32K に制限されています。

前述のように、メモリのサイズを変更して、必要なものを正確に使用することもできます。パフォーマンスが重要なコンテキストでは、回避できる場合は仮想メモリにページアウトしないことが重要です。

于 2009-05-08T12:04:53.143 に答える
3

スコープ外に出る以外に、スタック割り当てを解放する方法はありません。したがって、実際にグローバル割り当てを使用し、VM が実際のハード メモリを割り当てる必要がある場合、割り当てられ、プログラムが実行されるまでそこにとどまります。これは、どのプロセスもその仮想メモリの使用でのみ成長することを意味します (関数にはローカル スタック割り当てがあり、それらは「解放」されます)。

機能の範囲外になると、スタック メモリを「保持」することはできません。スタック メモリは常に解放されます。したがって、コンパイル時に使用するメモリ量を知っておく必要があります。

つまり、 int foo[1<<29] をいくつ持つことができるかということになります。最初のものは(32ビットで)メモリ全体を占有し、(嘘をつく:0x000000)になるため、2番目のものは0xffffffffまたはthereaoboutに解決されます。次に、3番目のものは何に解決されますか? 32bitポインタでは表現できないもの。(スタックの予約は、コンパイル時に部分的に解決され、部分的に実行時に、オフセットを介して解決されることに注意してください。これまたはその変数を割り当てたときに、スタック オフセットがどれだけプッシュされるか)。

したがって、答えは、 int foo [1<<29] を取得すると、他のローカルスタック変数を使用して適切な深さの関数を使用できなくなるということです。

于 2009-05-08T12:09:16.080 に答える
2

自分が何をしているのかわからない限り、これを避けるべきです。必要なだけのメモリを要求するようにしてください。使用されていないか、他のプログラムの邪魔になっていない場合でも、プロセス自体が台無しになる可能性があります。これには 2 つの理由があります。まず、特定のシステム、特に 32 ビット システムでは、まれにアドレス空間が早期に枯渇する可能性があります。さらに、多くのカーネルには、予約済み/仮想/使用されていないメモリに関するプロセスごとの制限があります。プログラムが実行時にメモリを要求する場合、この制限を超えるメモリを予約するように要求すると、カーネルはプロセスを強制終了できます。数 MB しか使用していないのに GB のメモリを予約しているため、malloc の失敗が原因でクラッシュまたは終了したプログラムを見てきました。

于 2009-05-08T12:06:56.287 に答える