从地址位置加载 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]加载堆指针=加载eax0xD38050,然后你可以使用[=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] 取消引用堆地址,访问分配内存的第一个元素。 &dataptrdataptr 值的地址。这对于像 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 位构建不支持内联汇编,因此通过使用内部函数,您的代码会变得稍微更可移植。其次,编译器在优化内联汇编方面做得相对较差,尤其是倾向于进行大量无意义的内存存储和加载。编译器在优化内部函数方面做得更好,这使您的代码 运行 更快(这就是使用内联汇编的全部意义!)。