根据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
答案 0 :(得分:7)
我不知道您是否误读了文档,或者是否
写得不好,但__thiscall
不表示this
指针在ECX中存储;它意味着指向对象的指针是
在ECX中传递。在较大的功能中,我看到它从一个移动
在函数的不同位置注册另一个,在某些地方
我已经看到它溢满了记忆。你不能指望它在
ECX。它将在哪里可以根据其他代码而改变
函数,以及传递给编译器的优化标志。
在你的情况下,你的问题更加复杂
函数是内联的,可能已内联。 (除了那个
_asm
可能会抑制内联。)不断传播(非常简单和
广泛使用的优化技术)几乎肯定意味着你的
致c.getX1()
只会使用42
,没有函数调用,也没有
访问c
无论如何。
一般来说,内联汇编程序是一个棘手的问题,正是因为你 不知道编译器用于什么寄存器。通常,在 除了实际的汇编程序指令外,还有指令 告诉编译器你是哪些寄存器和哪些变量 使用,你将能够在中引用变量本身 汇编程序和其他此类信息。除非你使用这些,否则你可以 对内联汇编程序假设非常非常少。
但每个编译器都有自己的规则。经常使用特殊语法。
例如mov eax, [cx].x
或mov 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指定的调用约定。我不认为这是一个错误,但如果您计划在内联函数中使用内联汇编,至少应该知道它。我的建议是你不要这样做。如果需要在类函数中进行内联汇编,请将声明和实现分开。