从地址位置加载 XMM 寄存器
Loading XMM registers from address location
我试图在 32 位操作系统上使用 load/store 内存 from/to 字符指针数组 XMM0 128 位寄存器。
我尝试的很简单:
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 = 0024F8380000000000F818E30055F158
第二次调试得到:
xmm0 = 0043FD6800000000002C18E3008CF158
所以这行一定有东西:
movdqu xmm0,[data]
我尝试改用这个:
movdqu xmm0,data
但我得到了相同的结果。
我认为问题在于我复制了地址而不是地址处的数据。但是 xmm0
寄存器中显示的值对于 32 位地址来说太大了,因此它必须从另一个地址复制内存。
我也尝试了一些在互联网上找到的其他说明,但结果相同。
这是我传递指针的方式还是我误解了 xmm 基础知识?
将不胜感激带有解释的有效解决方案。
虽然我找到了解决方案(终于在三个小时后),但我仍然想要一个解释:
ASM
{
push eax
mov eax,data
movdqu xmm0,[eax]
pop eax
}
为什么要将指针传递给 32 位寄存器?
#include <iostream>
int main()
{
char *dataptr = new char[33];
char datalocal[33];
dataptr[0] = 'a'; dataptr[1] = 0;
datalocal[0] = 'a'; datalocal[1] = 0;
printf("%p %p %c\n", dataptr, &dataptr, dataptr[0]);
printf("%p %p %c\n", datalocal, &datalocal, datalocal[0]);
delete[] dataptr;
}
输出:
0xd38050 0x7635bd709448 a
0x7635bd709450 0x7635bd709450 a
正如我们所见,动态指针data
实际上是一个指针变量(32位或64位0x7635BD709448
),包含一个指向堆的指针,0xD38050
。
局部变量直接是一个33个字符长的缓冲区,分配在地址0x7635BD709450
.
但是 datalocal
也可以用作 char *
值。
我有点搞不清楚正式的 C++ 解释是什么。在编写 C++ 代码时,这感觉很自然,dataptr[0] 是堆内存中的第一个元素(即,两次取消引用 dataptr),但在汇编程序中你看到了 dataptr
的真实本质,它是地址指针变量。所以你必须先通过mov eax,[data]
加载堆指针=加载eax
和0xD38050
,然后你可以使用[=24=加载0xD38050
的内容到XMM0中].
对于局部变量,没有变量的地址;符号 datalocal
已经是第一个元素的地址,因此 movdqu xmm0,[data]
将起作用。
在"wrong"情况下你仍然可以movdqu xmm0,[data]
;从 32 位变量加载 128 位不是 CPU 的问题。它会简单地继续读取超过 32 位并读取属于其他 variables/code 的另外 96 位。如果您在内存边界附近并且这是应用程序的最后一个内存页面,它将在无效访问时崩溃。
对齐在评论中提到了几次。这是有道理的;要通过 movdqu
访问内存,它应该对齐。检查您的 C++ 编译器内部函数。对于 Visual Studio 这应该有效:
__declspec(align(16)) char datalocal[33];
char *dataptr = _aligned_malloc(33, 16);
_aligned_free(dataptr);
关于我的 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++专家会从形式上把它撕成碎片...:)))
您的代码存在问题 data
是一个指针。汇编代码 movdqu xmm0,[data]
将 data
地址处的 16 个字节加载到寄存器 xmm0
中。这意味着包含指针值的 4 或 8 个字节以及内存中的任何字节。幸运的是指针地址在内存中正确对齐,否则会出现段错误。没有任何东西可以保证这种对齐方式。
使用自动数组的替代方案 char data[33];
会解决寻址问题(movqdu
会从数组加载数据)但不会解决对齐问题,您仍然可能会遇到违规,具体取决于编译器将数组与自动存储对齐。同样,不能保证正确对齐。
您找到的解决方案可能是一个很好的方法,但与 malloc()
不同的是,我不确定 new
返回的指针是否对任何对齐都有效。
这应该适用于所有情况:
#include <stdlib.h>
int main(void) {
char *data = malloc(33);
for (int i = 0; i < 32; i++) {
data[i] = 'a';
}
data[32] = 0;
__asm {
mov eax, data
movdqu xmm0, [eax]
}
free(data);
return 0;
}
正如 Peter Cordes 所评论的,对于这种事情,使用内在函数要好得多,即 mm_loadu_si128
。有两个主要原因:首先,64 位构建不支持内联汇编,因此通过使用内部函数,您的代码会变得稍微更可移植。其次,编译器在优化内联汇编方面做得相对较差,尤其是倾向于进行大量无意义的内存存储和加载。编译器在优化内部函数方面做得更好,这使您的代码 运行 更快(这就是使用内联汇编的全部意义!)。
我试图在 32 位操作系统上使用 load/store 内存 from/to 字符指针数组 XMM0 128 位寄存器。
我尝试的很简单:
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 = 0024F8380000000000F818E30055F158
第二次调试得到:
xmm0 = 0043FD6800000000002C18E3008CF158
所以这行一定有东西:
movdqu xmm0,[data]
我尝试改用这个:
movdqu xmm0,data
但我得到了相同的结果。
我认为问题在于我复制了地址而不是地址处的数据。但是 xmm0
寄存器中显示的值对于 32 位地址来说太大了,因此它必须从另一个地址复制内存。
我也尝试了一些在互联网上找到的其他说明,但结果相同。
这是我传递指针的方式还是我误解了 xmm 基础知识?
将不胜感激带有解释的有效解决方案。
虽然我找到了解决方案(终于在三个小时后),但我仍然想要一个解释:
ASM
{
push eax
mov eax,data
movdqu xmm0,[eax]
pop eax
}
为什么要将指针传递给 32 位寄存器?
#include <iostream>
int main()
{
char *dataptr = new char[33];
char datalocal[33];
dataptr[0] = 'a'; dataptr[1] = 0;
datalocal[0] = 'a'; datalocal[1] = 0;
printf("%p %p %c\n", dataptr, &dataptr, dataptr[0]);
printf("%p %p %c\n", datalocal, &datalocal, datalocal[0]);
delete[] dataptr;
}
输出:
0xd38050 0x7635bd709448 a
0x7635bd709450 0x7635bd709450 a
正如我们所见,动态指针data
实际上是一个指针变量(32位或64位0x7635BD709448
),包含一个指向堆的指针,0xD38050
。
局部变量直接是一个33个字符长的缓冲区,分配在地址0x7635BD709450
.
但是 datalocal
也可以用作 char *
值。
我有点搞不清楚正式的 C++ 解释是什么。在编写 C++ 代码时,这感觉很自然,dataptr[0] 是堆内存中的第一个元素(即,两次取消引用 dataptr),但在汇编程序中你看到了 dataptr
的真实本质,它是地址指针变量。所以你必须先通过mov eax,[data]
加载堆指针=加载eax
和0xD38050
,然后你可以使用[=24=加载0xD38050
的内容到XMM0中].
对于局部变量,没有变量的地址;符号 datalocal
已经是第一个元素的地址,因此 movdqu xmm0,[data]
将起作用。
在"wrong"情况下你仍然可以movdqu xmm0,[data]
;从 32 位变量加载 128 位不是 CPU 的问题。它会简单地继续读取超过 32 位并读取属于其他 variables/code 的另外 96 位。如果您在内存边界附近并且这是应用程序的最后一个内存页面,它将在无效访问时崩溃。
对齐在评论中提到了几次。这是有道理的;要通过 movdqu
访问内存,它应该对齐。检查您的 C++ 编译器内部函数。对于 Visual Studio 这应该有效:
__declspec(align(16)) char datalocal[33];
char *dataptr = _aligned_malloc(33, 16);
_aligned_free(dataptr);
关于我的 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++专家会从形式上把它撕成碎片...:)))
您的代码存在问题 data
是一个指针。汇编代码 movdqu xmm0,[data]
将 data
地址处的 16 个字节加载到寄存器 xmm0
中。这意味着包含指针值的 4 或 8 个字节以及内存中的任何字节。幸运的是指针地址在内存中正确对齐,否则会出现段错误。没有任何东西可以保证这种对齐方式。
使用自动数组的替代方案 char data[33];
会解决寻址问题(movqdu
会从数组加载数据)但不会解决对齐问题,您仍然可能会遇到违规,具体取决于编译器将数组与自动存储对齐。同样,不能保证正确对齐。
您找到的解决方案可能是一个很好的方法,但与 malloc()
不同的是,我不确定 new
返回的指针是否对任何对齐都有效。
这应该适用于所有情况:
#include <stdlib.h>
int main(void) {
char *data = malloc(33);
for (int i = 0; i < 32; i++) {
data[i] = 'a';
}
data[32] = 0;
__asm {
mov eax, data
movdqu xmm0, [eax]
}
free(data);
return 0;
}
正如 Peter Cordes 所评论的,对于这种事情,使用内在函数要好得多,即 mm_loadu_si128
。有两个主要原因:首先,64 位构建不支持内联汇编,因此通过使用内部函数,您的代码会变得稍微更可移植。其次,编译器在优化内联汇编方面做得相对较差,尤其是倾向于进行大量无意义的内存存储和加载。编译器在优化内部函数方面做得更好,这使您的代码 运行 更快(这就是使用内联汇编的全部意义!)。