【问题标题】:Visual C++ inline x86 assembly: Accessing "this" pointerVisual C++ 内联 x86 程序集:访问“this”指针
【发布时间】:2012-08-25 00:23:51
【问题描述】:

根据 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

【问题讨论】:

  • main()对应的反汇编是什么?尤其是你的函数声明为inline,你展示的汇编代码甚至不一定会被使用。
  • @Greg Hewgill:刚刚在我的帖子中插入了 main 函数的代码。
  • 看起来编译器已经优化了整个对象,完全不存在了。这有点令人惊讶,因为它不知道您在 _asm 块中实际在做什么。
  • 尝试使构造函数not内联,看看对象构造是否仍然被优化掉。
  • @Greg Hewgill:是的,对第一个非 asm Get 函数的调用只是直接扩展内联到代码中,但第二个函数是使用 call 指令显式调用的。向函数添加内联 asm 会改变调用约定,这似乎还是有点奇怪:?

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


【解决方案1】:

我不知道您是否误读了文档,或者是否 写得不好,但__thiscall 确实 意味着this 指针存储在ECX中;这意味着指向对象的指针是 在 ECX 中通过。在更大的功能中,我已经看到它从一个 在函数的不同位置注册到另一个,并且在某些 案例,我已经看到它溢出到记忆中。你不能指望它在里面 心电图。它的位置可能会根据 函数,以及传递给编译器的优化标志。

在您的情况下,由于您的 函数是内联的,并且可能已经内联。 (除了那个 _asm 可能会抑制内联。) 持续传播(一个非常简单且 广泛使用的优化技术)几乎肯定意味着您的 调用c.getX1() 将只使用42,没有函数调用,也没有 访问c 随便什么。

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

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

编辑:

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

【讨论】:

  • 谢谢你的链接,我去看看。
【解决方案2】:

问题是,根据我对 MS 编译器的了解,编译器不知道 [ecx]this-&gt;x 相同,因此编译器不知道成员变量是访问(通过函数跟踪数据流很棘手)。

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

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

【讨论】:

    【解决方案3】:

    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,这也将消除未来的挫败感。

    【讨论】:

    • 我刚试过,但(正如预期的那样)它没有任何作用。此外,如果您查看 main 函数中的代码,包含 inline asm (GetX2) 的函数是不是扩展的。不过,您是对的,首先在 MSVC 中使用内联 asm 可能并不是一个好主意。
    • @Dragonion: 1) 你使用什么编译器?我在 MSVC8 上进行了测试。 2)它是否扩展无关紧要,优化器能够跨函数边界进行优化。 __declspec 在我的情况下或其他一些情况下(例如与没有 LTCG 的 obj 链接)阻止它完成其工作。
    【解决方案4】:

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

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

    【讨论】:

    • 您是对的,编译器没有将“this”放在 ECX 中(我的问题是事实上为什么它不这样做),但使其非内联不会解决除非 Visual C++ 完全忽略 C++ 标准。
    • @Dragonion _asm 不是标准的一部分。如果你看一下你的反汇编,编译器已经完全内联了这个对象,以至于它不存在于内存中。
    • 我知道,但无论是否内联,函数的行为都必须相同。你能想象一个函数如果被内联则返回 1,否则返回 0?
    • @Dragonion _asm 不在标准中,所以无论编译器做什么仍然符合标准。
    • @Dragonion 不需要,因为该函数不使用this
    【解决方案5】:

    这就是我设法使函数正确工作的方法(即在 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 指定的调用约定。我不认为这是一个错误,但如果您打算在内联函数中使用内联汇编,您至少应该意识到这一点。我的建议是你不要这样做。如果您需要在类函数中进行内联汇编,请将声明和实现分开。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-25
      相关资源
      最近更新 更多