2012-05-13 1 views
48

今日、私はCコードで私の友人を助けていましたが、私は彼になぜそれが起こったのか説明できないという奇妙な振る舞いを見つけました。 TSVファイルには整数のリストがあり、各行にはintが付いています。最初の行はリストの行数です。配列型とmallocで割り付けられた配列の相違

また、非常に単純な「readfile」を持つcファイルもありました。最初の行は、nに読み出された行の数が、そこの初期た:

int list[n] 

、最後に関数fscanfのnのforループ。

小さなn(〜100.000)の場合は、すべて問題ありませんでした。しかし、nが大きかった(10^6)と、segfaultが発生することがわかりました。

最後に、私たちも非常に大きなnは、

int *list = malloc(n*sizeof(int)) 

、すべてのリストの初期化を変更したときも。

これがなぜ起こったのか誰かが説明できますか? list = malloc(n * sizeof(int))の使用を開始したときに止められたint list [n]でsegfaultを引き起こしていたのは何ですか?

答えて

109

ここにはいくつかの異なる部分があります。

最初は最初のバージョンでは

int array[n]; 

int* array = malloc(n * sizeof(int)); 

として配列を宣言との差である、自動記憶域期間を持つオブジェクトを宣言しています。つまり、配列を呼び出す関数が存在する限り、配列は存続します。 2番目のバージョンでは、ダイナミックストレージ期間を持つメモリが取得されています。つまり、明示的に割り当てを解除するまで存在します(free)。

番目のバージョンは、ここに働くことの理由は、Cは通常、コンパイルされたかの実装の詳細です。通常、Cメモリはスタック(関数呼び出しとローカル変数用)とヒープ(malloc edオブジェクト用)を含むいくつかの領域に分割されています。通常、スタックのサイズはヒープのサイズよりはるかに小さくなります。通常は8MBのようなものです。結果として、巨大な配列を割り当てようとすると、巨大な配列を割り当てようとすると、巨大な配列を割り当てようとすると、segfaultがスタックの記憶領域を超えてしまう可能性があります。一方、ヒープは通常、大きなサイズ(たとえば、システム上の空き領域)が大きいため、大きなオブジェクトを作成してもメモリ不足エラーは発生しません。

一般に、Cの可変長配列には注意が必要です。これらは簡単にスタックサイズを超えることがあります。サイズが小さいこと、または実際に短時間だけアレイが必要な場合以外は、mallocを推奨します。

希望すると便利です。

+1

非常に解明的な答え...ありがとう! –

+1

偉大な答え!スピードの違いもあるのだろうか? –

+1

参照の局所性の影響により、スタック割り当て配列がアクセスする方が高速で、 'malloc'自体がスタックポインタをバンプするよりもはるかに遅いと思われます。しかし、実際には、手近な課題に対してより適切なアプローチを使用することが最善です。 – templatetypedef

2

int list [n]はスタックにデータを格納し、mallocはヒープにデータを格納します。

スタックは限られており、ヒープははるかに大きくなりますが、スペースはあまりありません。

1

int list[n]はVLAであり、ヒープ上ではなくスタック上に割り当てます。それを解放する必要はありません(関数呼び出しの終わりに自動的に解放されます)。すぐに割り当てられますが、記憶スペースは非常に限られています。ヒープには大きな値を割り当てる必要があります。

1

この宣言は、

int list[n] 

malloc関数は、ヒープ上に割り当てて、スタック上のメモリを割り当てます。

スタックサイズは通常ヒープよりも小さいので、スタックにあまりにも多くのメモリを割り当てると、スタックオーバーフローが発生します。

も参照してくださいthis answer for further information

0

あなたがmallocを使用して割り当てると、メモリがヒープからではなく、サイズがはるかに限られているスタックから割り当てられます。通常はかなり小さいですスタック、上n整数の

8
int list[n] 

割り当てスペース。スタック上のメモリを使用する方がはるかに高速ですが、それは非常に小さく、巨大な配列を割り当てるか再帰をあまりにも深く行うようなことをすれば、スタックをオーバーフローさせる(つまりあまりにも多くのメモリを割り当てる)のは簡単です。この方法で割り当てられたメモリの割り当てを手動で解除する必要はありません。配列が範囲外になったときにコンパイラによって行われます。一方

mallocスタックに比べて通常非常に大きいヒープ、空間を割り当てます。ヒープにはもっと多くのメモリを割り当てる必要がありますが、ヒープ上のメモリをスタックに割り当てるのはずっと時間がかかりますが、使用を終了したらfreeで手動で割り当てを解除する必要がありますそれ。

+1

「スタック上のメモリを使用する方がはるかに高速です」という場合、ここでは「割り当て」または「アクセス」を意味しますか? AFAIK、スタックの割り当てははるかに高速ですが、アクセス(読み取り/書き込み)にも当てはまりますか?ありがとう – dragonxlwang

1

それが最も可能性がありますあなたの実装では、典型的な実装を持っていると仮定すると:あなたのスタック上

int list[n] 

割り当てられたリスト、場所など:あなたのヒープ上の

int *list = malloc(n*sizeof(int)) 

割り当てられたメモリ。

スタックの場合、これらのサイズが大きくなる可能性があります(まったく成長できない場合)。ヒープの場合にはまだ限界がありますが、RAM +スワップ+アドレス空間では、大部分は(大まかに)広がります。

0

Linuxの場合は、ulimit -sをより大きな値に設定することができます。これはスタックの割り当てにも有効です。 スタックにメモリを割り当てると、そのメモリは関数の実行が終了するまで保持されます。ヒープにメモリを割り当てる場合(mallocを使用)、いつでも(関数の実行が終了する前であっても)いつでもメモリを解放することができます。

一般に、ヒープは大きなメモリ割り当てに使用する必要があります。

0
int array[n]; 

これは静的に割り当てられた配列の例であり、コンパイル時に配列のサイズが分かります。配列はスタックに割り当てられます。

int *array(malloc(sizeof(int)*n); 

これは、動的に割り当てられた配列の一例であり、アレイのサイズは、実行時にユーザに知られているであろう。配列はヒープ上に割り当てられます。

関連する問題