26

スタックに割り当てられた変数が大きすぎるために発生したと思われる、かなり奇妙なスタック オーバーフローをデバッグしています。次のことを明確にしたいと思います。

次の関数があるとします。

void function()
{
    char buffer[1 * 1024];
    if( condition ) {
       char buffer[1 * 1024];
       doSomething( buffer, sizeof( buffer ) );
    } else {
       char buffer[512 * 1024];
       doSomething( buffer, sizeof( buffer ) );
    }
 }

コンパイラに依存し、オプティマイザの決定にも依存することは理解していますが、これらのローカル変数にメモリを割り当てるための一般的な戦略は何ですか?

関数に入るとすぐに最悪の場合 (1 + 512 キロバイト) が割り当てられるのか、それとも最初に 1 キロバイトが割り当てられ、条件に応じて 1 キロバイトまたは 512 キロバイトが追加で割り当てられるのか?

4

5 に答える 5

15

多くのプラットフォーム/ABI では、関数に入ると、スタックフレーム全体 (すべてのローカル変数のメモリを含む) が割り当てられます。他のものでは、必要に応じてメモリをビットごとにプッシュ/ポップするのが一般的です。

もちろん、スタックフレーム全体が一度に割り当てられる場合でも、異なるコンパイラが異なるスタック フレーム サイズを決定する可能性があります。あなたの場合、一部のコンパイラは最適化の機会を逃し、コードの異なるブランチ (配列とあなたのケースの両方) にあるものであっても、すべてのローカル変数に一意のメモリを割り当てます。関数を介したパスに必要な最大メモリのみを割り当てます(この場合のパスなので、512kb ブロックを割り当てるだけで十分です)。プラットフォームの機能を知りたい場合は、逆アセンブリを参照してください。1 * 1024512 * 1024else

しかし、メモリのチャンク全体がすぐに割り当てられても驚かないでしょう。

于 2011-08-17T07:10:49.323 に答える
11

私はLLVMをチェックしました:

void doSomething(char*,char*);

void function(bool b)
{
    char b1[1 * 1024];
    if( b ) {
       char b2[1 * 1024];
       doSomething(b1, b2);
    } else {
       char b3[512 * 1024];
       doSomething(b1, b3);
    }
}

収量:

; ModuleID = '/tmp/webcompile/_28066_0.bc'
target datalayout = "e-p:64:64:64-i1:8:8-i8:8:8-i16:16:16-i32:32:32-i64:64:64-f32:32:32-f64:64:64-v64:64:64-v128:128:128-a0:0:64-s0:64:64-f80:128:128-n8:16:32:64"
target triple = "x86_64-unknown-linux-gnu"

define void @_Z8functionb(i1 zeroext %b) {
entry:
  %b1 = alloca [1024 x i8], align 1               ; <[1024 x i8]*> [#uses=1]
  %b2 = alloca [1024 x i8], align 1               ; <[1024 x i8]*> [#uses=1]
  %b3 = alloca [524288 x i8], align 1            ; <[524288 x i8]*> [#uses=1]
  %arraydecay = getelementptr inbounds [1024 x i8]* %b1, i64 0, i64 0 ; <i8*> [#uses=2]
  br i1 %b, label %if.then, label %if.else

if.then:                                          ; preds = %entry
  %arraydecay2 = getelementptr inbounds [1024 x i8]* %b2, i64 0, i64 0 ; <i8*> [#uses=1]
  call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay2)
  ret void

if.else:                                          ; preds = %entry
  %arraydecay6 = getelementptr inbounds [524288 x i8]* %b3, i64 0, i64 0 ; <i8*> [#uses=1]
  call void @_Z11doSomethingPcS_(i8* %arraydecay, i8* %arraydecay6)
  ret void
}

declare void @_Z11doSomethingPcS_(i8*, i8*)

alloca関数の上部に3 が表示されます。

そのうちの 1 つしか使用されないため、IR で と が一緒に折りたたまれていないことに少しがっかりしていることを認めなければなりませんb2b3

于 2011-08-17T08:02:44.313 に答える
10

複数のスタック オブジェクトを同じアドレスに割り当てるため、この最適化は「スタック カラーリング」と呼ばれます。これは、LLVM で改善できることがわかっている領域です。現在、LLVM はスピル スロットのレジスタ アロケータによって作成されたスタック オブジェクトに対してのみこれを行います。これを拡張してユーザー スタック変数も処理したいのですが、IR で値の有効期間をキャプチャする方法が必要です。

これを行う方法の大まかなスケッチがここにあります: http://nondot.org/sabre/LLVMNotes/MemoryUseMarkers.txt

これに関する実装作業が進行中であり、いくつかの部分がメインラインに実装されています。

-クリス

于 2011-08-17T18:20:13.157 に答える
4

ローカル(スタック)変数は、スタックフレームと同じスペースに割り当てられます。関数が呼び出されると、スタックポインタがスタックフレーム用の「スペースを空ける」ように変更されます。これは通常、1回の呼び出しで行われます。ローカル変数でスタックを消費すると、スタックオーバーフローが発生します。

いずれにせよ、〜512キロバイトはスタックには大きすぎます。を使用して、これをヒープに割り当てる必要がありますstd::vector

于 2011-08-17T08:50:18.293 に答える
0

As you say, it is compiler dependent, but you could consider using alloca to overcome this. The variables would still be allocated on the stack, and still automatically freed as they go out of scope, but you take control over when and if the stack space is allocated.

While use of alloca is typically discouraged, it does have its uses in situations such as the above.

于 2011-08-17T07:51:54.230 に答える