_declspec(align(16))不能将指针对齐到16字节。

4

我正在尝试使用SSE函数__mm_load_128,我对SSE非常陌生,如果我在某个地方犯了一些愚蠢的错误,请原谅。

下面是代码:

void one(__m128i *arr, char *temp)
{
    // SSE needs 16 byte alignment.
    _declspec (align(16)) __m128i *tmp = (__m128i*) temp;

    if (((uintptr_t)tmp & 15) == 0)
        printf("Aligned pointer");
    else 
        printf("%d", ((uintptr_t)tmp & 15)); // This prints as 12

    arr[0] = _mm_load_si128(tmp);
}

我在Visual Studio中遇到了一个错误:

0xC0000005:访问地址 0xFFFFFFFF 时发生访问冲突。

0xFFFFFFFF 看起来不对劲,我做错了什么吗?

arr参数被初始化为_m128i arr[5] = { 0 }

另一种方法是使用_mm_loadu_128,它可以正常工作,但据我所知,它应该生成movdqu指令,但实际生成的汇编代码如下:

    arr[0] = _mm_loadu_si128(tmp);
00D347F1  mov         eax,dword ptr [tmp]  
00D347F4  movups      xmm0,xmmword ptr [eax]  
00D347F7  movaps      xmmword ptr [ebp-100h],xmm0  
00D347FE  mov         ecx,10h  
00D34803  imul        edx,ecx,0  
00D34806  add         edx,dword ptr [arr]  
00D34809  movups      xmm0,xmmword ptr [ebp-100h]  
00D34810  movups      xmmword ptr [edx],xmm0 

谢谢大家,从回答中我意识到我犯了几个错误。

  1. 使用 _alinged_malloc 对齐源代码

  2. 进行优化编译。

  3. 使用 C++ 强制类型转换而不是 C 的强制类型转换。


4
你没有做任何事来对齐源地址。你试图对齐指针而不改变存储的地址。你应该确保temp所指向的缓冲区被正确对齐。另外,在C++代码中应避免使用C风格的类型转换。 - user7860670
2
@VTT: (__m128i*) 是内嵌函数的标准风格。通常使用 reinterpret_cast<__m128i*>(temp) 会显得过于冗长,而且英特尔的内嵌函数名称已经足够长了。此外,__m128i 可以别名任何其他类型,因此它是特殊的。 - Peter Cordes
1
如果您想要看到正常的汇编输出,请不要禁用优化进行编译。发出“imul edx,ecx,0”只是一种可笑的清零“edx”的方式。它正在执行“movups”加载,但然后将其溢出到堆栈上的临时变量中并重新加载(使用对齐的加载),然后再次存储到实际想要结果的位置。就像我说的那样,如果您想要汇编代码看起来像在编写C ++时想象的那样,请启用优化。 - Peter Cordes
2
这里的遗憾之处在于,它不会在新版本的MSVC和ICC上崩溃,因为它们通常会使用未对齐的访问方式。因此,您将获得难以检测到的性能损失,而不是崩溃。不幸的是,MSVC开发人员表示这是设计上的问题。 - Mysticial
1个回答

8
我看到了三个问题:
  1. 这段代码是严格的C代码,而不是C++代码,这本身不是问题,但是该问题被标记为C++。
  2. 你没有区分指针的对齐方式和指针指向的内容的对齐方式。
  3. 当代码控制流程处于one时,无法更改arrtemp的对齐方式。
让我们先关注第2点——有一个指针,还有指针所指的内容。我猜你已经知道这两者之间的区别了。
基本上,当你写_declspec (align(16)) __m128i *tmp时,你告诉程序:

在堆栈上分配指针tmp时,请确保tmp的第一个字节在可被16整除的地址(在堆栈上)上分配。

所以很好,tmp本身对齐到了16,它完全不影响tmp指向的内容。你需要使temp指向对齐的数据。这可以通过以下方式实现:
  1. 使用正确的alignas关键字在堆栈上分配数据(alignas(16) char my_buffer[16*100];
  2. 使用能够分配对齐数据的内存分配函数动态分配数据,例如aligned_alloc或MSVC的_aligned_malloc(需要_aligned_free)。请参见如何解决AVX加载/存储操作的32字节对齐问题?
你无法事后对齐内存,它必须在一开始就对齐分配。确保由temp传递的数据已经对齐,或者如果不能要求调用方传递对齐的数据,则使用未对齐的加载/存储。

4
如果你将其编译为C++,它就是C++。你可以说它以类似于C的风格编写,但这并不改变编译器解释代码的方式。C和C++之间有微妙的差别。也许这段代码来自于一个C的例子,而发帖者正在C++中使用它。 - Peter Cordes
2
你也可以在C++程序中写入__asm{mov eax, 8},但这并不意味着汇编语言是C++。 - David Haim
2
我大部分同意这个观点,但仍然认为你应该用不同的措辞来表达你的第一点。如果你想批评在C++代码中使用C风格,那没问题。但这并不意味着它就是C。联合类型游戏仍将是未定义行为(除了我认为MSVC确实定义了它,就像GNU C++一样)。但你可以争论,在某些方面,它确实使得MSVC++程序的汇编部分。MSVC内联汇编与MASM有细微差别(例如,它接受0xdeadbeef以及0deadbeefH)。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接