Visual C ++内联x86程序集:访问“this”指针

时间:2012-08-30 09:43:53

标签: c++ visual-c++ x86 this inline-assembly

根据MSDN文档,当使用类函数的默认__thiscall调用约定时,“this”指针存储在ECX中。尽管在翻译常规C ++代码时确实存在这种情况,但在尝试使用内联汇编访问“this”时遇到了问题。

这是测试程序:

#include <cstdio>

class TestClass
{
    long x;

    public:
        inline TestClass(long x):x(x){}

    public:
        inline long getX1(){return x;}
        inline long getX2()
        {
            _asm
            {
                mov eax,dword ptr[ecx]
            }
        }
};
int main()
{
    TestClass c(42);

    printf("c.getX1() = %d\n",c.getX1());
    printf("c.getX2() = %d\n",c.getX2());

    return 0;
}

两个Get函数的翻译如下:

?getX1@TestClass@@QAEJXZ (public: long __thiscall TestClass::getX1(void)):
  00000000: 8B 01              mov         eax,dword ptr [ecx]
  00000002: C3                 ret

?getX2@TestClass@@QAEJXZ (public: long __thiscall TestClass::getX2(void)):
  00000000: 8B 01              mov         eax,dword ptr [ecx]
  00000002: C3                 ret

我认为可以说这两个功能完全相同。不过,这是程序的输出:

c.getX1() = 42
c.getX2() = 1

显然,当调用第二个Get函数时,“this”存储在ECX中,所以我的问题是:我如何确保包含内联汇编的类函数遵循调用约定和/或调用与常规/非内联函数相同的方式?

编辑:主要功能翻译如下:

_main:
  00000000: 51                 push        ecx
  00000001: 6A 2A              push        2Ah
  00000003: 68 00 00 00 00     push        offset $SG3948
  00000008: E8 00 00 00 00     call        _printf
  0000000D: 83 C4 08           add         esp,8
  00000010: 8D 0C 24           lea         ecx,[esp]
  00000013: E8 00 00 00 00     call        ?getX2@TestClass@@QAEJXZ
  00000018: 50                 push        eax
  00000019: 68 00 00 00 00     push        offset $SG3949
  0000001E: E8 00 00 00 00     call        _printf
  00000023: 33 C0              xor         eax,eax
  00000025: 83 C4 0C           add         esp,0Ch
  00000028: C3                 ret

5 个答案:

答案 0 :(得分:7)

我不知道您是否误读了文档,或者是否 写得不好,但__thiscall 表示this 指针在ECX中存储;它意味着指向对象的指针是 在ECX中传递。在较大的功能中,我看到它从一个移动 在函数的不同位置注册另一个,在某些地方 我已经看到它溢满了记忆。你不能指望它在 ECX。它将在哪里可以根据其他代码而改变 函数,以及传递给编译器的优化标志。

在你的情况下,你的问题更加复杂 函数是内联的,可能已内联。 (除了那个 _asm可能会抑制内联。)不断传播(非常简单和 广泛使用的优化技术)几乎肯定意味着你的 致c.getX1()只会使用42,没有函数调用,也没有 访问c无论如何。

一般来说,内联汇编程序是一个棘手的问题,正是因为你 不知道编译器用于什么寄存器。通常,在 除了实际的汇编程序指令外,还有指令 告诉编译器你是哪些寄存器和哪些变量 使用,你将能够在中引用变量本身 汇编程序和其他此类信息。除非你使用这些,否则你可以 对内联汇编程序假设非常非常少。

但每个编译器都有自己的规则。经常使用特殊语法。 例如mov eax, [cx].xmov eax, x之类的内容可能是。c.x Microsoft内联汇编程序需要什么。无论如何,没有办法 从你所写的,编译器可能推断出它 您正在访问c。而且因为所有其他用途都已被淘汰 通过不断传播,它甚至可能是一个非常差的编译器 生成变量x

编辑:

FWIW:Microsoft内联汇编程序的文档位于 http://msdn.microsoft.com/en-us/library/4ks26t93%28v=vs.71%29.aspx。一世 没有仔细看过它,但有一节关于“使用C或 __asm块中的C ++符号“。这可能会解释你怎么做 以编译器将知道的方式访问内联汇编程序中的x 已访问{{1}}。

答案 1 :(得分:4)

问题是,从我看到的MS编译器来看,编译器不知道[ecx]this->x相同,所以编译器不知道正在访问成员变量(通过函数跟踪数据流很棘手)。

编译器优化了对对象构造函数的调用,并使用传递给构造函数的常量内联getX1。这意味着在调用getX2时未正确构造对象,因为从编译器的视图来看,函数getX2不访问任何成员,因此不需要正确构造。在MS编译器中,即使使用[ecx]TestClass.x,我还没有看到告诉编译器正在使用成员变量的方法。

并且,多次提到的,inline经常被编译器忽略,编译器更好地了解何时最好内联代码。在这种情况下,带有_asm块的函数不会内联,其他函数会被内联/重写。

答案 2 :(得分:2)

this实际上存储在ecx内,至少是对象在未被优化的情况下所在的地址:

00000010: 8D 0C 24           lea         ecx,[esp]

问题是优化器并不真正理解汇编代码,因此代码正确性的责任在于您。它只是删除了对象,因为它看到它可以将42内联到printf调用,例如:

printf("c.getX1() = %d\n",42);

要使其有效,请将getX2定义为noinline

long __declspec(noinline) getX2() { ... }

这使优化器将其视为一个完整的黑盒子,因此它不会对是否访问c对象做出任何假设。这确实对我有用,但没有记录。

相反,我建议您不要对MSVC使用内联汇编,它甚至不支持64位编译。使用MASM,这也将消除未来的挫败感。

答案 3 :(得分:1)

由于您已内联函数,编译器不知道在调用asm代码之前必须正确设置ecx,尤其是因为函数的其余部分不使用任何对象属性或方法

首先尝试声明和定义非inline方法。将getX2定义为在不同的转换单元中定义的非内联非成员函数可能更好,因此编译器的优化机会受到限制。

答案 4 :(得分:1)

这就是我设法让函数正确工作的方式(即在ECX中传递“this”):

<强> testclass.hpp

class TestClass
{
    long x;

    public:
        inline TestClass(long x):x(x){}

    public:
        long getX1();
        long getX2();
};

<强> testclass.cpp

#include "testclass.hpp"

long TestClass::getX1()
{
    return x;
}
long TestClass::getX2()
{
    _asm
    {
        mov eax,dword ptr[ecx]
    }
}

<强> testmain.cpp

#include <cstdio>
#include "testclass.hpp"

int main()
{
    TestClass c(42);

    printf("c.getX1() = %d\n",c.getX1());
    printf("c.getX2() = %d\n",c.getX2());

    return 0;
}

<强>输出

c.getX1() = 42
c.getX2() = 42

问题是MSVC 2010中的内联类函数不一定遵循MSDN指定的调用约定。我不认为这是一个错误,但如果您计划在内联函数中使用内联汇编,至少应该知道它。我的建议是你不要这样做。如果需要在类函数中进行内联汇编,请将声明和实现分开。