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

Dra*_*ion 6 c++ x86 this inline-assembly visual-c++

根据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;
}
Run Code Online (Sandbox Code Playgroud)

两个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
Run Code Online (Sandbox Code Playgroud)

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

c.getX1() = 42
c.getX2() = 1
Run Code Online (Sandbox Code Playgroud)

显然,当调用第二个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
Run Code Online (Sandbox Code Playgroud)

Jam*_*nze 7

我不知道你是否会误读的文档,还是它的写得不好,但__thiscall确实不是意味着this 指针存储在ECX; 这意味着指向对象的指针 在ECX中传递.在较大的函数中,我已经看到它在函数的不同位置从一个寄存器移动到另一个寄存器,在某些情况下,我已经看到它溢出到内存中.你不能指望它在ECX中.它将在哪里根据函数中的其他代码进行更改,并将优化标志传递给编译器.

在您的情况下,由于您的函数是内联的,并且可能已经内联,因此问题更加复杂.(除非这 _asm可能会抑制内联.)常量传播(一种非常简单且广泛使用的优化技术)几乎肯定意味着您的调用c.getX1()只会使用42,没有函数调用且无法访问c任何内容.

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

但每个编译器都有自己的规则.经常使用特殊语法.喜欢的东西mov eax, [cx].x,例如,或者mov eax, x,可能是微软的内联汇编的需求是什么.无论如何,你编写的文件可能无法推断出你正在访问的内容c.x.并且由于所有其他用途已通过常量传播消除,因此即使生成变量也是一个非常差的编译器c.

编辑:

FWIW:Microsoft内联汇编程序的文档位于 http://msdn.microsoft.com/en-us/library/4ks26t93%28v=vs.71%29.aspx.我没有详细介绍它,但有一节关于"在__asm块中使用C或C++符号".这可能会解释如何x以编译器知道x已访问的方式访问内联汇编程序.


Dra*_*ion 1

这就是我如何使该函数正确工作(即在 ECX 中传递“this”):

测试类.hpp

class TestClass
{
    long x;

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

    public:
        long getX1();
        long getX2();
};
Run Code Online (Sandbox Code Playgroud)

测试类.cpp

#include "testclass.hpp"

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

测试主.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;
}
Run Code Online (Sandbox Code Playgroud)

输出

c.getX1() = 42
c.getX2() = 42
Run Code Online (Sandbox Code Playgroud)

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