我尝试在32位操作系统上使用XMM 0 128位寄存器从/向char指针数组加载/存储内存。
我尝试的很简单:
int main() {
char *data = new char[33];
for (int i = 0; i < 32; i++)
data[i] = 'a';
data[32] = 0;
ASM
{
movdqu xmm0,[data]
}
delete[] data;
}
问题是,这似乎不起作用。第一次调试Win32应用程序时,我得到了:
xmm0 = 0024F838000000000F818E30055F158
第二次调试时,我得到了:
xmm0 = 0043FD6800000000002C18E3008CF158
所以这句话一定有什么含义:
movdqu xmm0,[data]
我试着用这个代替:
movdqu xmm0,data
但我得到了同样的结果
我认为的问题是我复制了地址而不是地址处的数据。但是,xmm0
寄存器显示的值对于32位地址来说太大,因此它必须从另一个地址复制内存。
我也试了一些我在网上找到的其他说明,但结果是一样的。
是我传递指针的方式还是我误解了xmm的一些基础知识?
将理解具有解释的有效解决方案。
即使我找到了解决方案(终于在三个小时后),我仍然想要一个解释:
ASM
{
push eax
mov eax,data
movdqu xmm0,[eax]
pop eax
}
为什么要将指针传递给32位寄存器?
2条答案
按热度按时间rwqw0loc1#
你的代码的问题是
data
是一个指针。汇编代码movdqu xmm0,[data]
将data
地址处的16个字节加载到寄存器xmm0
中。这意味着4或8个字节包括指针的值和内存中跟随的任何字节。你很幸运,指针地址在内存中正确对齐,否则你会得到一个分段错误。没有什么能保证这种一致性。使用自动数组
char data[33];
的替代方案可以解决寻址问题(movqdu
将从数组加载数据),但不能解决对齐问题,您仍然可能会遇到冲突,具体取决于编译器如何将数组与自动存储对齐。同样,不能保证正确对齐。您找到的解决方案可能是一个很好的方法,但与
malloc()
不同,我不确定new
返回的指针是否对大于指定类型的对齐有效。此外,new
和malloc()
都不能保证您打算使用的SIMD指令所需的16字节对齐。大多数系统都有内存分配API,以确保16或更宽的对齐:POSIX系统有
posix_memalign
:Linux系统也支持
memalign
:首选的解决方案是在
<stdlib.h>
中定义的C标准函数aligned_alloc
,在C11中添加,但可能无法在所有系统上使用:如果您的系统上有此功能,您可以这样写:
正如Peter Cordes所评论的那样,使用intrinsic来处理这类事情要好得多,即
mm_loadu_si128
。有两个主要原因:首先,内联汇编语法不是标准的,并且在不同的编译器之间以及在32位和64位版本之间都有所不同,因此通过使用intrinsic,代码的可移植性会稍微提高一些。第二,编译器在优化内联汇编方面做得相对较差,特别是倾向于做大量无意义的内存存储和加载。编译器在优化intrinsic方面做得更好,这使您的代码运行得更快(这是使用内联汇编的全部要点!).a0x5cqrl2#
输出:
正如我们所看到的,动态指针
data
实际上是一个指针变量(32位或64位在0x7635BD709448
),包含一个指向堆的指针0xD38050
。局部变量直接是一个33个字符长的缓冲区,分配在地址
0x7635BD709450
。但是
datalocal
也可以作为char *
值使用。我有点搞不清楚C对这个问题的正式解释是什么。在编写C代码时,这感觉很自然,dataptr[0]是堆内存中的第一个元素(也就是说,解引用dataptr两次),但在汇编程序中,您可以看到
dataptr
的真实本质,它是指针变量的地址。因此,您必须首先通过mov eax,[data]
= loadseax
和0xD38050
加载堆指针,然后您可以通过使用[eax]
将0xD38050
的内容加载到XMM 0中。有了局部变量,就没有带有它的地址的变量;符号
datalocal
已经是第一个元素的地址,所以movdqu xmm0,[data]
将工作。在“错误”的情况下,您仍然可以执行
movdqu xmm0,[data]
;从一个32位变量加载128位并不是CPU的问题。它将简单地继续读取32位以外的数据,并读取属于其他变量/代码的另外96位。如果您在内存边界附近,并且这是应用程序的最后一个内存页,则它将在无效访问时崩溃。在评论中多次提到对齐。这是一个有道理的观点为了通过
movdqu
访问存储器,它应该被对准。检查你的C++编译器内部。对于Visual Studio,这应该可以工作:关于我的C解释:也许我从一开始就错了。
dataptr
是dataptr符号的值,即堆地址。然后dataptr[0]
解引用堆地址,访问已分配内存的第一个元素。&dataptr
是dataptr
值的地址。这对于像dataptr = nullptr;
这样的语法也是有意义的,在这种语法中,您将nullptr值存储到dataptr变量中,而不是覆盖dataptr符号地址。对于
datalocal[]
来说,访问纯datalocal
基本上没有意义,就像在datalocal = 'a';
中一样,因为它是一个数组变量,所以你应该总是提供[]
索引。而&datalocal
是这样一个数组的地址。纯datalocal
是一个别名快捷方式,可以更容易地使用数组等进行点数学运算,也有char *
类型,但如果纯datalocal
会抛出语法错误,仍然可以编写C代码(使用&datalocal
作为指针,datalocal[..]
作为元素),并且它完全适合dataptr
逻辑。结论:从一开始你的例子就错了,因为在汇编语言中,
[data]
正在加载data
的值,这是new
返回的堆指针。这是我自己的解释,现在一些C++Maven会来从形式的Angular 把它撕成碎片...:)))