3

Jim Trevor のPL クラスの「 Cyclone: A safe dialect of C 」を読みながら、C と Cyclone を理解しようとしています。Trevor は、安全でない go-to ステートメントの例を次のように示しています。

int z;
{ int x = 0xBAD; goto L; }
{ int *y = &z;
L: *y = 3; // Possible segfault
}

Trevor は、上記のコードの安全性の問題を次のように説明しています。

多くのコンパイラは、ブロックに入るときにブロックのローカル変数をスタックに割り当て、ブロックが出るときにストレージの割り当てを解除 (ポップ) します (ただし、これは C 標準では義務付けられていません)。例をこのようにコンパイルすると、プログラムが最初のブロックに入ると、x のスペースがスタックに割り当てられ、値 0xBAD で初期化されます。goto は、2 番目のブロックの中央にジャンプし、ポインター y の内容への代入に直接ジャンプします。y は 2 番目のブロックで宣言された最初の (唯一の) 変数であるため、割り当てでは y がスタックの一番上にあると想定されます。残念ながら、それはまさに x が割り当てられた場所であるため、プログラムは場所 0xBAD に書き込もうとし、おそらくセグメンテーション違反を引き起こします。

go toここでその発言が問題になる理由がわかりません。問題は、初期化されていないポインター Z からの予測できない動作であるようint * yですint* y。 Z によって参照されるメモリ。Trevor の論文が Z と X の両方が何らかの形で 0xBAD を参照することを暗示している理由がわかりません。C は最初のブロックの新しいスタック フレームを作成しませんか (Trevor が説明したように): したがって、メモリ内の新しいフレームに 0xBAD を書き込みます (Z によって参照されるメモリ内の場所ではありません)。

4

4 に答える 4

5

ここで go to ステートメントが問題になる理由がわかりません。

goto Ly( ywill not set to )の初期化をバイパスする&zため、 who-knows-where-it's-pointing に割り当てるときに問題が発生し*yます。

問題は、初期化されていないポインター Z からの予測できない動作のようです

いいえ。ポインター&zは実際には有効です。int値は初期化されzていませんが、読み取ろうとしないため問題ありません。あなたは実際にそれを上書きしようとしています。

2 番目のブロックの先頭で、int * y は Z のアドレスで埋められます。

そこが肝心だ。 goto Lそれをバイパスします。

トレバーの論文が Z と X の両方が何らかの形で 0xBAD を参照していることを暗示している理由がわかりません

Trevor はここで 2 番目の潜在的な問題を暗示していると思いますが、実際にそれを示すコンパイラがいくつあるかはわかりません。でブロックを離れるとgoto、スタック ポインター ( ESPx86 など) は理論的にはデクリメントされない場合があります。の初期化をスキップするとy、スタック ポインタもインクリメントされない場合があります。そのため、コンパイラがスタック ポインタを使用してローカルを参照する場合 ( EBPx86 などのフレーム ポインタの代わりに)、そのようなコンパイラは、理論上、あたかも起こったかxのように を間違える可能性があります。yint* y = 0xBAD

于 2012-11-12T18:26:26.880 に答える
1

ブロックを削除し、値の初期化と宣言を分離すると、問題を理解しやすくなります。

int z;
int *y;
goto L;
y = &z;
L: 
*y = 42;

これは基本的に元のサンプルで起こっていることですが、もう少し明確です。ここで行y = &zは実行されないyため、未定義の場所を指しているため、設定は安全ではありません。

于 2012-11-12T18:31:44.643 に答える
1

言語に関する限り、プログラムの動作は未定義です。のgoto初期化をスキップしますy。ポインター オブジェクトは存在しますが、定義された場所を指していません。逆参照yには未定義の動作があります。

しかし、コードをもう少し詳しく見て、どのように動作するかについていくつかの (不当な) 仮定を行います:

int z;
{ 
    int x = 0xBAD; goto L; 
}   
{ 
    int *y = &z;
    L: *y = 3; // Possible segfault
}

ローカル変数は (通常) スタックに割り当てられます。各ローカル変数は、制御がその定義を含むブロックの最後に到達すると存在しなくなります。

私が思うに、最初のブロックはintobjectを作成しx、それに値を割り当てます0xBAD。 がそのブロックから制御を転送xすると存在しなくなりますが、値はスタックの一番上にまだ存在する可能性があります。goto0xBAD

制御を 2 番目のgotoブロックに転送します。の初期化はスキップしますがy、割り当てはスキップします。制御が直接ブロックに入るか、ステートメントを介して入るかに関係なく、ポインター オブジェクトyは引き続きスタックに割り当てられます。値がスタックの一番上に残っていgotoた場合、同じ場所に簡単に割り当てることができます。初期化がスキップされたので、値がそのまま残される可能性があります(または、表現を構成するビットがそのまま残り、ポインター値として解釈されます)。0xBADy0xBADyint0xBADy

そのため、割り当ては値をメモリ location*y = 3;に格納しようとします。30xBAD

これがおそらく、変数を定義、初期化、および使用する根拠xですy

しかし、実際には、ここ (最初の段落以降) で説明した動作は、C 標準では必要ありませんサンプル内のオブジェクトのように、並列ブロック内のオブジェクトは、同じメモリ位置に格納される場合と格納されない場合があります。の初期化x、およびスタックへの割り当てでさえ、最適化コンパイラによって簡単に削除できます。また、ローカル変数を「スタック」に割り当てる必要さえありません (スタック ポインターによって管理されるメモリの連続した領域という意味で)。C標準では、「スタック」という言葉さえ使用していません。連続したスタックは、ローカル変数に必要なセマンティクスを実装する最も自然な方法ですが、必須ではありません。もちろん、intint*は同じサイズである必要はありません。

結論:*y = 3;が実行されると、 の値yは初期化されていないガベージ (意図的に「ランダム」という言葉を避けています) であるため、逆参照の動作yは未定義です。特定の仮定を考えると、ガベージはたまたま のように見えるかもしれませ0xBADんが、それは実際には問題ではありません。

于 2012-11-12T19:03:56.570 に答える
0

あなたが言ったように、問題は変数yが初期化される前にアクセスされる可能性があることです。あなたが提供したコード スニペットは、問題を示す可能性が高い 1 つの方法にすぎません。

これをGCCで-Wallオプション付きでコンパイルすると、 warning 'y' is used uninitialized in this function. g++ で C++ コードとしてコンパイルすると、実際にはエラーになります。

test.cc:8:3: error: jump to label ‘L’
test.cc:6:25: error:   from here
test.cc:7:10: error:   crosses initialization of ‘int* y’

この場合yは POD 型ですが、代わりにコンストラクターを持つクラスである場合、 はコンストラクgotoターをスキップします。C++ 言語仕様では、これはすべての場合において違法であると規定されています。

于 2012-11-12T18:29:51.060 に答える