【问题标题】:Where is the 'this' pointer stored in computer memory?“this”指针存储在计算机内存中的什么位置?
【发布时间】:2013-05-16 10:54:55
【问题描述】:

'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 存储在内存中的什么位置?

【问题讨论】:

    标签: c++ this-pointer


    【解决方案1】:

    最简单的方法是将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);
    

    请注意,上面的转换是简化的,希望能让我的观点更清楚一点。不需要用“哇,方法重载的编组在哪里,嗯?”来填充 cmets,请类型反对。 :)

    这将问题转化为“参数存储在哪里?”,答案当然是“视情况而定”。 :)

    它通常在堆栈上,但也可以在寄存器中,或者编译器认为对目标架构有益的任何其他机制。

    【讨论】:

    • 很好的解释,但我认为__thiscall 最常使用ecx 来存储this 指针。
    • AFAIK,这实际上方法的隐藏参数,我错了吗?
    • @Spook:因为它对用户完全透明,所以这个论点毫无意义。大概是这样实现的,但是有什么变化呢?
    • @riv OP 想知道 :)
    • 并非每个系统都有名为 ecx 的东西。
    【解决方案2】:

    其他答案很好地解释了典型编译器如何实现this(通过将其作为隐式第一个参数传递给函数)。

    我认为了解 C++ ISO 规范对此的明确说明也很有用。根据 C++03 ISO 规范,§9.3.2/1:

    在非静态 (9.3) 成员函数的主体中,关键字 this 是一个非左值表达式,其值是调用该函数的对象的地址。

    请务必注意,this 不是变量 - 它是一个表达式,与表达式 1 + 2 * 3 是一个表达式的方式非常相似。这个表达式的值几乎可以存储在任何地方。编译器可能将它放在堆栈中,并将其作为隐式参数传递给函数,或者它可能将它放在寄存器中,并且可以想象它可以将它放在堆或数据段中。 C++ 规范特意为实现提供了一些灵活性。

    我认为“语言律师”的答案是“这是完全由实现定义的,而且this 在技术上不是指针,而是计算为指针的表达式。”

    希望这会有所帮助!

    【讨论】:

      【解决方案3】:

      this 通常作为方法的隐藏参数传递(不同调用约定的唯一区别是如何)。

      如果你打电话:

      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 总是 作为函数的隐式第一个参数传递。这就是大多数实现使this 工作的方式,但并不要求这实际上必须发生。其他实现也是可能的。
      • 技术上这是对的,但是我还没有遇到除了隐式参数之外的其他实现,在绝大多数情况下你可以放心地假设这种情况。但是,为了技术上正确,我将 always 更改为 通常,这应该足够宽松。
      【解决方案4】:

      this 是一个右值(你不能获取它的地址),所以它不会 (必然)完全占用内存。取决于编译器 和目标架构,它通常会在一个寄存器中:i0 在 Sparc 上,在 Intel 上带有 MSVC 的 ECX 等。当优化器是 活跃,它甚至可以四处走动。 (我见过不同的 向 MSVC 注册)。

      【讨论】:

        【解决方案5】:

        this 的行为主要类似于函数参数,因此将存储在堆栈中,或者 - 如果架构的二进制调用约定允许 - 在寄存器中。

        【讨论】:

          【解决方案6】:

          this 未存储在明确定义的位置!它指向的对象存储在某处,并且具有明确定义的地址,但地址本身没有特定的家庭地址。它在程序中被传达。不仅如此,该指针还可以有多个副本。

          在以下虚构的init 函数中,对象注册自己以接收事件和计时器回调(使用虚构的事件源对象)。所以注册后,多了两份this

          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 指针。它存储在机器寄存器中,或者存储在函数激活的堆栈帧中的内存位置中。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2021-10-17
            • 2010-11-20
            • 2021-01-17
            • 2014-01-31
            • 1970-01-01
            相关资源
            最近更新 更多