今日、私は友人のCコードを手伝っていましたが、なぜそうなったのかを説明できない奇妙な振る舞いを見つけました。 TSVファイルには整数のリストがあり、各行にintがあります。最初の行は、リストの行数でした。
また、非常に単純な「readfile」を持つcファイルもありました。最初の行は行数であるnに読み取られ、次の初期化が行われました。
int list[n]
最後に、fscanfを使用したnのforループ。
小さいn(〜100.000まで)の場合、すべてが正常でした。ただし、nが大きい場合(10 ^ 6)、セグメンテーション違反が発生することがわかりました。
最後に、リストの初期化を
int *list = malloc(n*sizeof(int))
そして、nが非常に大きい場合でも、すべてうまくいきます。
誰かがこれが起こった理由を説明できますか? list = malloc(n * sizeof(int))の使用を開始したときに停止したint list [n]でセグメンテーション違反が発生した原因は何ですか?
ここにはいくつかの異なる作品があります。
1つ目は、配列を次のように宣言することの違いです。
int array[n];
そして
int* array = malloc(n * sizeof(int));
最初のバージョンでは、自動ストレージ期間を持つオブジェクトを宣言しています。つまり、配列を呼び出す関数が存在する限り、配列は存続します。 2番目のバージョンでは、動的ストレージ期間でメモリを取得します。つまり、free
で明示的に割り当て解除されるまで存在し続けます。
ここで2番目のバージョンが機能する理由は、Cが通常どのようにコンパイルされるかの実装の詳細です。通常、Cメモリは、スタック(関数呼び出しとローカル変数用)およびヒープ(malloc
edオブジェクト用)を含むいくつかの領域に分割されます。通常、スタックのサイズはヒープよりもはるかに小さくなります。通常、8MBのようなものです。その結果、巨大な配列を割り当てようとすると
int array[n];
その後、スタックのストレージ容量を超えて、セグメンテーション違反が発生する可能性があります。一方、ヒープのサイズは通常非常に大きいため(たとえば、システム上で空いているスペースと同じくらい)、大きなオブジェクトをmalloc
ingしてもメモリ不足エラーは発生しません。
一般に、Cの可変長配列には注意してください。スタックサイズを簡単に超える可能性があります。サイズが小さいことや、配列を短時間しか必要としない場合を除き、malloc
を優先します。
お役に立てれば!
int list[n]
スタックのn
整数にスペースを割り当てます。これは通常かなり小さいです。スタック上のメモリの使用は、代替よりもはるかに高速ですが、非常に小さく、巨大な配列を割り当てたり、再帰を深くしすぎたりすると、スタックをオーバーフローさせる(つまり、メモリを過剰に割り当てる)のは簡単です。この方法で割り当てられたメモリの割り当てを手動で解除する必要はありません。これは、配列がスコープ外になったときにコンパイラによって行われます。
一方、malloc
はheapにスペースを割り当てます。これは通常、スタックと比較して非常に大きいです。ヒープに大量のメモリを割り当てる必要がありますが、スタックにあるよりもヒープにメモリを割り当てる方がはるかに遅く、free
を使用して手動で割り当てを解除する必要があります使い終わったら。
int list [n]はデータをスタックに格納し、mallocはデータをヒープに格納します。
スタックは限られており、スペースはあまりありませんが、ヒープははるかに大きくなります。
int list[n]
はVLAであり、ヒープではなくスタックに割り当てます。解放する必要はなく(関数呼び出しの最後に自動的に解放されます)、すぐに割り当てられますが、発見したように、ストレージスペースは非常に限られています。ヒープにより大きな値を割り当てる必要があります。
この宣言は、スタック上のメモリを割り当てます
int list[n]
mallocはヒープに割り当てます。
通常、スタックサイズはヒープよりも小さいため、スタックにメモリを割り当てすぎると、スタックオーバーフローが発生します。
詳細はこの回答 も参照してください
実装に典型的な実装があると仮定すると、ほとんどの場合:
int list[n]
スタック上の割り当てられたリスト。
int *list = malloc(n*sizeof(int))
ヒープに割り当てられたメモリ。
スタックの場合、通常、これらが成長できる大きさに制限があります(成長できる場合)。ヒープの場合はまだ制限がありますが、RAM +スワップ+アドレス空間によって大きく制限され、(大部分)制限される傾向があります。
int array[n];
これは静的に割り当てられた配列の例であり、コンパイル時に配列のサイズがわかります。そして、配列はスタックに割り当てられます。
int *array(malloc(sizeof(int)*n);
これは動的に割り当てられた配列の例であり、配列のサイズは実行時にユーザーに認識されます。そして、配列はヒープに割り当てられます。
Linuxを使用している場合は、ulimit -sをより大きな値に設定できます。これは、スタック割り当てでも機能する場合があります。スタックにメモリを割り当てると、そのメモリは関数の実行が終了するまで残ります。 (mallocを使用して)ヒープにメモリを割り当てると、いつでも(関数の実行が終了する前でも)メモリを解放できます。
一般的に、ヒープは大きなメモリ割り当てに使用する必要があります。
malloc
を使用して割り当てる場合、メモリはスタックからではなく、ヒープから割り当てられます。これは、サイズがはるかに制限されています。