VC ++中一个非常特殊的代码序列生成了以下指令(对于Win32):
unpcklpd xmm0,xmmword ptr [ebp-40h]
出现2个问题:
(1)就我理解的intel手册而言,unpcklpd接受128对齐的内存地址作为第二个参数。如果地址相对于堆栈帧,则不能强制对齐。这真的是一个编译器错误吗?
(2)只有在从调试器运行时才会执行此指令时抛出异常,即使这样也不总是如此。即使附加到进程并执行此代码也不会抛出。这怎么可能?
抛出的特殊异常是0xFFFFFFFF处的访问冲突,但AFAIK只是未对齐的代码。
[编辑:] 这里有一些来源可以证明代码生成错误 - 但通常不会导致崩溃。 (这主要是我想知道的)
[编辑2:] 代码示例现在重现实际崩溃。这个也在调试器外崩溃 - 我怀疑是因为调试器在不同的典型基地址启动程序而发生差异。
// mock.cpp
#include <stdio.h>
struct mockVect2d
{
double x, y;
mockVect2d() {}
mockVect2d(double a, double b) : x(a), y(b) {}
mockVect2d operator + (const mockVect2d& u) {
return mockVect2d(x + u.x, y + u.y);
}
};
struct MockPoly
{
MockPoly() {}
mockVect2d* m_Vrts;
double m_Area;
int m_Convex;
bool m_ParClear;
void ClearPar() { m_Area = -1.; m_Convex = 0; m_ParClear = true; }
MockPoly(int len) { m_Vrts = new mockVect2d[len]; }
mockVect2d& Vrt(int i) {
if (!m_ParClear) ClearPar();
return m_Vrts[i];
}
const mockVect2d& GetCenter() { return m_Vrts[0]; }
};
struct MockItem
{
MockItem() : Contour(1) {}
MockPoly Contour;
};
struct Mock
{
Mock() {}
MockItem m_item;
virtual int GetCount() { return 2; }
virtual mockVect2d GetCenter() { return mockVect2d(1.0, 2.0); }
virtual MockItem GetItem(int i) { return m_item; }
};
void testInner(int a)
{
int c = 8;
printf("%d", c);
Mock* pMock = new Mock;
int Flag = true;
int nlr = pMock->GetCount();
if (nlr == 0)
return;
int flr = 1;
if (flr == nlr)
return;
if (Flag)
{
if (flr < nlr && flr>0) {
int c = 8;
printf("%d", c);
MockPoly pol(2);
mockVect2d ctr = pMock->GetItem(0).Contour.GetCenter();
// The mess happens here:
// ; 74 : pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
//
// call ? Vrt@MockPoly@@QAEAAUmockVect2d@@H@Z; MockPoly::Vrt
// movdqa xmm0, XMMWORD PTR $T4[ebp]
// unpcklpd xmm0, QWORD PTR tv190[ebp] **** crash!
// movdqu XMMWORD PTR[eax], xmm0
pol.Vrt(0) = ctr + mockVect2d(1.0, 0.);
pol.Vrt(1) = ctr + mockVect2d(0., 1.0);
}
}
}
void main()
{
testInner(2);
return;
}
如果您愿意,请下载一个准备好的vcxproj,其中包含从here设置的所有开关。这也包括完整的ASM。
答案 0 :(得分:2)
更新:现在是confirmed VC++ compiler bug,希望在VS2015 RTM中解决。
编辑:与许多其他人一样,连接报告现在是垃圾。但是,编译器错误似乎在2015年更新3中的VS2017 - 而不是中得到了解决。
答案 1 :(得分:1)
由于没有其他人加强,我打算开枪。
1)如果地址相对于堆栈帧,则不能强制对齐。这真的是一个编译器错误吗?
我不确定你是否真的不能强制对齐堆栈变量。请考虑以下代码:
struct foo
{
char a;
int b;
unsigned long long c;
};
int wmain(int argc, wchar_t* argv[])
{
foo moo;
moo.a = 1;
moo.b = 2;
moo.c = 3;
}
查看main的启动代码,我们看到:
00E31AB0 push ebp
00E31AB1 mov ebp,esp
00E31AB3 sub esp,0DCh
00E31AB9 push ebx
00E31ABA push esi
00E31ABB push edi
00E31ABC lea edi,[ebp-0DCh]
00E31AC2 mov ecx,37h
00E31AC7 mov eax,0CCCCCCCCh
00E31ACC rep stos dword ptr es:[edi]
00E31ACE mov eax,dword ptr [___security_cookie (0E440CCh)]
00E31AD3 xor eax,ebp
00E31AD5 mov dword ptr [ebp-4],eax
将<_declspec(align(16))添加到moo中
01291AB0 push ebx
01291AB1 mov ebx,esp
01291AB3 sub esp,8
01291AB6 and esp,0FFFFFFF0h <------------------------
01291AB9 add esp,4
01291ABC push ebp
01291ABD mov ebp,dword ptr [ebx+4]
01291AC0 mov dword ptr [esp+4],ebp
01291AC4 mov ebp,esp
01291AC6 sub esp,0E8h
01291ACC push esi
01291ACD push edi
01291ACE lea edi,[ebp-0E8h]
01291AD4 mov ecx,3Ah
01291AD9 mov eax,0CCCCCCCCh
01291ADE rep stos dword ptr es:[edi]
01291AE0 mov eax,dword ptr [___security_cookie (12A40CCh)]
01291AE5 xor eax,ebp
01291AE7 mov dword ptr [ebp-4],eax
显然编译器(VS2010为Win32编译了调试),认识到我们需要特定的代码对齐,采取措施确保它可以提供。
2)仅在从调试器运行时才会执行此指令时抛出异常,即使这样也不总是如此。即使附加到进程并执行此代码也不会抛出。怎么会这样?
所以,有几点想法:
“即便不是总是” - 当你跑步时,不要站在你的肩膀上,我不能肯定地说。然而,似乎有可能只是随机的机会,堆栈可以通过你需要的对齐来创建。默认情况下,x86使用4字节堆栈对齐。如果您需要16字节对齐,则您有四分之一的镜头。
至于其余部分(来自https://msdn.microsoft.com/en-us/library/aa290049%28v=vs.71%29.aspx#ia64alignment_topic4):
在x86架构上,操作系统不会使对齐故障对应用程序可见。 ...你也会在对齐故障上遭受性能下降,但是它会比Itanium严重得多,因为硬件会对内存进行多次访问以检索未对齐的数据。
TLDR:使用__declspec(align(16))可以为您提供所需的对齐方式,即使对于堆栈变量也是如此。对于未对齐的访问,操作系统将捕获异常并为您处理(以性能为代价)。
Edit1:回应下面的前两条评论:
基于MS的docs,您对堆栈参数的对齐是正确的,但他们也提出了一个解决方案:
您无法指定功能参数的对齐方式。当数据那个 对象属性是通过堆栈上的值传递的 对齐由调用约定控制。 如果数据对齐 在被调用函数中很重要,将参数正确复制 使用前对齐记忆。
你的微软连接样本和代码都没有为我生成相同的代码(我只在vs2010上),所以我无法测试。但是从您的样本中获取此代码:
struct mockVect2d
{
double x, y;
mockVect2d(double a, double b) : x(a), y(b) {}
似乎对齐mockVect2d或2个双打可能有所帮助。