メソッド呼び出しからの呼び出しと戻りの場合にスタック アンダーフローを引き起こす C++ の簡単な例は何ですか?
私は、呼び出し規約、つまりthiscall
、stdcall
およびcdecl
スタックをクリーンアップする方法に精通しています。スタック アンダーフローは、コンパイラによって生成されたコードによって自動的に処理されませんか?
スタック アンダーフローで問題が発生する状況はどのようなものですか?
メソッド呼び出しからの呼び出しと戻りの場合にスタック アンダーフローを引き起こす C++ の簡単な例は何ですか?
私は、呼び出し規約、つまりthiscall
、stdcall
およびcdecl
スタックをクリーンアップする方法に精通しています。スタック アンダーフローは、コンパイラによって生成されたコードによって自動的に処理されませんか?
スタック アンダーフローで問題が発生する状況はどのようなものですか?
stdcall
これが実際に起こっていることを確認できる唯一の方法は、 (または呼び出し先がスタックをクリーンアップすることを指定する他の呼び出し規約)を使用する関数を宣言し、 (または任意の)として指定された関数ポインターを介して関数を呼び出すcdecl
場合です。スタックが呼び出し元によって消去される他の呼び出し規約)。これを行うと、呼び出された関数が戻る前にスタックをポップし、呼び出し元もスタックをポップして、アンダーフローやひどい事態につながります。
メンバー関数の特定のケースでは、呼び出し規約は通常 と呼ばれthiscall
、呼び出し元と呼び出し先のどちらがスタックを消去するかは、コンパイラによって異なります。
呼び出し規約の詳細については、こちらを参照してください。
あなたがデータ構造スタックとその中のアンダーフローの問題について話しているのか、それとも何か他のことについて話しているのかわかりません。stack(data structure) underflow
問題に関する限り、ここに説明があります。
stack
後入れ先出し (LIFO) の抽象データ型およびデータ構造です。スタックは、任意の抽象データ型を要素として持つことができますが、プッシュ、ポップ、およびスタック トップの3 つの基本的な操作のみによって特徴付けられます。
プッシュ操作は、スタックの一番上に新しいアイテムを追加するか、スタックが空の場合はスタックを初期化します。スタックがいっぱいで、指定されたアイテムを受け入れるのに十分なスペースがない場合、スタックはオーバーフロー状態にあると見なされます。pop 操作は、スタックの一番上からアイテムを削除します。
ポップは、以前に隠されたアイテムを明らかにするか、空のスタックになりますが、スタックが空の場合、アンダーフロー状態になります (スタックに削除するアイテムが存在しないことを意味します)。
スタックトップ操作は、データを最上位から取得し、削除せずにユーザーに返します。スタックが空の場合、スタック トップ操作でも同様のアンダーフロー状態が発生する可能性があります。
スタックの実装例を考えてみましょう:
template <class Item> class Stack
{
public:
bool isEmpty() const;
size_t size() const;
Item pop();
void push(const Item& it);
private:
};
次に、このスタックで次の操作が実行されているとします。
C++ command resulting stack
------------------------------------------------
Stack<int> S;
_____ (empty stack of ints)
S.push(7);
| 7 | <-- top
-----
S.push(2);
| 2 | <-- top
| 7 |
-----
S.push(73);
|73 | <-- top
| 2 |
| 7 |
-----
S.pop();
| 2 | <-- top
| 7 | -----
S.pop();
-----
S.pop();
| 7 | <-- top
-----
S.pop();
----- (empty)
S.pop();
ERROR "stack underflow"
通常、これはコンパイラによって処理されます。実際には、誤ってスタック アンダーフローを引き起こす可能性があると私が考える唯一の方法は、ある呼び出し規約で実装されたメソッドを別の呼び出し規約を使用しているかのように呼び出すことです。
呼び出しスタックのアンダーフローが発生する可能性がある状況に陥った場合、プログラムはそれが発生する前に暴力的な死を遂げる可能性があります。少なくとも、関数呼び出しの仕組みを理解していれば正確です。
基本的に、それが発生する唯一の方法は、呼び出し先がスタックをクリーンアップする関数を呼び出し、それがあまりにも多くの値をポップする場合です...呼び出し元が関数が2つのパラメーターを受け入れると信じているが、呼び出し先が実際には3つを取る場合、これは起こるかもしれません。バリアントは、呼び出し規則が間違っている場合に発生する可能性があるように、呼び出し先がスタックをクリーンアップし、次に呼び出し元がスタックを再度クリーンアップする関数です。どちらの状況でも、リンク先に行ってマングルされた名前が間違っていると問題が発生する可能性がありますが、本当に運が悪いだけかもしれません。
いずれにせよ、重要なことは、関数呼び出しの後、スタックが想定よりも 1 バイト以上短いということです。理論的には、プログラムは続行し、正しい量のデータをポップしますが、予想よりも 1 バイト以上不足したままになります。最終的に、ポップするデータがなくなり、スタック アンダーフローが発生します。
ただし、スタックを参照する場合、アドレスは先頭からの相対です。そのため、スタックの先頭から 3 バイトの場合、コンパイラは [スタックの先頭 + 3] で特定のオブジェクトを探します。スタックが予想よりも短くなってしまった場合でもそれを行い、間違った場所でそのオブジェクトを探します。オブジェクトがまだそこにあると仮定すると... 誤って既に飛び出してしまった可能性があります。どんな機能を使っていても最後に到達すると、これと同じ理由で正しい戻りアドレスを見つけることができないかもしれませんが、たとえそうであったとしても、すべてのオブジェクトが突然壊れてしまうというのは、かなり悲惨な状況です。 .
警告: これはすべて、最新のシステムが、私が 10 年前に使用していた古いマイクロコントローラーと同じように動作するという仮定に基づいています。多分彼らは今より賢いです。