web-dev-qa-db-ja.com

'this'ポインターはコンピューターのメモリのどこに保存されていますか?

「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はメモリのどこに保存されますか?

63

他の答えは、典型的なコンパイラーがthisをどのように実装するかを説明する非常に良い仕事をしました(それを暗黙の最初のパラメーターとして関数に渡すことによって)。

C++ ISO仕様がこれについて明示的に述べていることを確認することも役立つと思います。 C++ 03 ISO仕様によると、§9.3.2/ 1:

非静的(9.3)メンバー関数の本体では、キーワードthisは非値の式であり、その値は関数が呼び出されるオブジェクトのアドレスです。

thisnot変数であることに注意することが重要です-これはであり、ほとんど同じですその式1 + 2 * 3は式です。この式の値は、ほとんどどこにでも格納できます。コンパイラーmightはそれをスタックに入れて、暗黙のパラメーターとして関数に渡すか、またはmightレジスタに入れます。おそらく、ヒープまたはデータセグメントに入れることができます。 C++仕様では、意図的に実装にある程度の柔軟性を与えています。

「language-lawyer」の答えは「これは完全に実装定義であり、さらにthisは技術的にはポインタではなく、ポインタに評価される式」だと思います。

お役に立てれば!

63
templatetypedef

最も簡単な方法は、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、メソッドのオーバーロードのマーシャリングはどこにあるの?」タイプの反対意見を記入する必要はありません。 :)

それは質問を「引数はどこに保存されますか?」に変換し、答えはもちろん「依存する」です。 :)

多くの場合、スタック上にありますが、それはレジスターにある場合もあれば、コンパイラーがターゲット・アーキテクチャーに適していると考えるその他のメカニズムの場合もあります。

78
unwind

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はこのメソッドの通常のパラメータとして扱われます。

33
Spook

thisは右辺値(アドレスを取得することはできません)であるため、(必ずしも)メモリをまったく使用しません。コンパイラとターゲットアーキテクチャに応じて、それはしばしばレジスタにあります:Sparc上のi0、Intel上のMSVCを備えたECXなど。オプティマイザがアクティブなとき、それは移動することさえできます。 (MSVCのさまざまなレジスターで見ました)。

18
James Kanze

thisは、ほとんどが関数の引数のように動作するため、スタックに格納されるか、アーキテクチャのバイナリ呼び出し規約で許可されている場合は、レジスタに格納されます。

9
MvG

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関数を呼び出し、barupdateという別の関数を呼び出します。各関数のアクティブ化レベルには、thisポインターがあります。これは、マシンレジスタ、または関数アクティベーションのスタックフレーム内のメモリロケーションに格納されます。

0
Kaz