「this」ポインタはメモリのどこに正確に格納されていますか?スタック、ヒープ、またはデータセグメントに割り当てられていますか?
_#include <iostream>
using namespace std;
class ClassA
{
int a, b;
public:
void add()
{
a = 10;
b = 20;
cout << a << b << endl;
}
};
int main()
{
ClassA obj;
obj.add();
return 0;
}
_
上記のコードでは、メンバー関数add()
を呼び出しており、レシーバーオブジェクトは暗黙的に「this」ポインターとして渡されます。 this
はメモリのどこに保存されますか?
他の答えは、典型的なコンパイラーがthis
をどのように実装するかを説明する非常に良い仕事をしました(それを暗黙の最初のパラメーターとして関数に渡すことによって)。
C++ ISO仕様がこれについて明示的に述べていることを確認することも役立つと思います。 C++ 03 ISO仕様によると、§9.3.2/ 1:
非静的(9.3)メンバー関数の本体では、キーワード
this
は非値の式であり、その値は関数が呼び出されるオブジェクトのアドレスです。
this
はnot変数であることに注意することが重要です-これは式であり、ほとんど同じですその式1 + 2 * 3
は式です。この式の値は、ほとんどどこにでも格納できます。コンパイラーmightはそれをスタックに入れて、暗黙のパラメーターとして関数に渡すか、またはmightレジスタに入れます。おそらく、ヒープまたはデータセグメントに入れることができます。 C++仕様では、意図的に実装にある程度の柔軟性を与えています。
「language-lawyer」の答えは「これは完全に実装定義であり、さらにthis
は技術的にはポインタではなく、ポインタに評価される式」だと思います。
お役に立てれば!
最も簡単な方法は、this
を常に自動的に渡される非表示の追加の引数と考えることです。
したがって、次のような架空の方法:
size_t String::length(void) const
{
return strlen(m_string);
}
フードの下では、実際には次のようになります。
size_t String__length(const String *this)
{
return strlen(this->m_string);
}
そして次のような呼び出し:
{
String example("hello");
cout << example.length();
}
次のようになります:
cout << String__length(&example);
上記の変換は単純化されていることに注意してください。うまくいけば、私のポイントを少し明確にすることができます。コメントに「whaaa、メソッドのオーバーロードのマーシャリングはどこにあるの?」タイプの反対意見を記入する必要はありません。 :)
それは質問を「引数はどこに保存されますか?」に変換し、答えはもちろん「依存する」です。 :)
多くの場合、スタック上にありますが、それはレジスターにある場合もあれば、コンパイラーがターゲット・アーキテクチャーに適していると考えるその他のメカニズムの場合もあります。
this
は通常、メソッドの隠し引数として渡されます(異なる呼び出し規約での唯一の違いはhowです)。
あなたが呼び出す場合:
myClass.Method(1, 2, 3);
コンパイラーは次のコードを生成します。
Method(&myClass, 1, 2, 3);
最初のパラメーターは実際にはthis
へのポインターです。
次のコードを確認してみましょう。
class MyClass
{
private:
int a;
public:
void __stdcall Method(int i)
{
a = i;
}
};
int main(int argc, char *argv[])
{
MyClass myClass;
myClass.Method(5);
return 0;
}
__stdcall
を使用することにより、コンパイラーにすべてのパラメーターをスタック経由で渡すように強制しました。その後、デバッガーを起動してアセンブリコードを検査すると、次のようなものが見つかります。
myClass.Method(5);
00AA31BE Push 5
00AA31C0 lea eax,[myClass]
00AA31C3 Push eax
00AA31C4 call MyClass::Method (0AA1447h)
ご覧のように、メソッドのパラメーターはスタックを介して渡され、次にmyClassのアドレスがeaxレジスターにロードされ、再びスタックにプッシュされます。つまり、this
はこのメソッドの通常のパラメータとして扱われます。
this
は右辺値(アドレスを取得することはできません)であるため、(必ずしも)メモリをまったく使用しません。コンパイラとターゲットアーキテクチャに応じて、それはしばしばレジスタにあります:Sparc上のi0、Intel上のMSVCを備えたECXなど。オプティマイザがアクティブなとき、それは移動することさえできます。 (MSVCのさまざまなレジスターで見ました)。
this
は、ほとんどが関数の引数のように動作するため、スタックに格納されるか、アーキテクチャのバイナリ呼び出し規約で許可されている場合は、レジスタに格納されます。
this
は明確な場所に保存されていません!それが指すオブジェクトはどこかに格納され、明確に定義されたアドレスを持っていますが、アドレス自体には特定のホームアドレスがありません。プログラム内で伝達されます。それだけでなく、そのポインタの多くのコピーが存在する可能性があります。
次の架空のinit
関数では、オブジェクトは自身を登録して、イベントとタイマーコールバックを受け取ります(架空のイベントソースオブジェクトを使用)。したがって、登録後、this
の2つの追加コピーがあります。
void foo_listener::init()
{
g_usb_events.register(this); // register to receive USB events
g_timer.register(this, 5); // register for a 5 second timer
}
私は関数のアクティブ化チェーンです。thisポインターの複数のコピーもあります。オブジェクトobj
があり、そのfoo
関数を呼び出すとします。その関数は同じオブジェクトのbar
関数を呼び出し、bar
はupdate
という別の関数を呼び出します。各関数のアクティブ化レベルには、this
ポインターがあります。これは、マシンレジスタ、または関数アクティベーションのスタックフレーム内のメモリロケーションに格納されます。