在C和C ++中未定义有符号整数溢出。但是__m128i
的各个字段中的有符号整数溢出呢?换句话说,这种行为是在英特尔标准中定义的吗?
#include <inttypes.h>
#include <stdio.h>
#include <stdint.h>
#include <emmintrin.h>
union SSE2
{
__m128i m_vector;
uint32_t m_dwords[sizeof(__m128i) / sizeof(uint32_t)];
};
int main()
{
union SSE2 reg = {_mm_set_epi32(INT32_MAX, INT32_MAX, INT32_MAX, INT32_MAX)};
reg.m_vector = _mm_add_epi32(reg.m_vector, _mm_set_epi32(1, 1, 1, 1));
printf("%08" PRIX32 "\n", (uint32_t) reg.m_dwords[0]);
return 0;
}
[myria@polaris tests]$ gcc -m64 -msse2 -std=c11 -O3 sse2defined.c -o sse2defined
[myria@polaris tests]$ ./sse2defined
80000000
请注意,SSE2 __m128i
的4字节大小的字段被视为已签名。
答案 0 :(得分:7)
这个问题大概有三个问题(不是以低调的方式,在#34;你缺乏理解和#34;那种方式......这就是为什么我猜你来了这里)。
1)您询问的是具体的实施问题(使用SSE2)而不是标准。您已回答了自己的问题&#34; C&#34;中未定义有符号整数溢出。
2)当你处理c内在函数时,你甚至不能用C编程!这些是插入汇编指令。它是以便携式方式进行的,但您的数据不再是有符号整数。它是传递给SSE内在函数的向量类型。然后你将它转换为一个整数并告诉C你想要查看该操作的结果。 当您进行投射时,无论发生什么字节都是您将看到的并且与C标准中的带符号算术无关。
3)只有两个错误的假设。我对错误的数量做了一个假设并且错了。
如果编译器插入SSE指令(比如在循环中),情况会有所不同。现在编译器保证结果与带符号的32位操作相同......除非存在未定义的行为(例如溢出),在这种情况下它可以做任何喜欢的事情。
另请注意,undefined并不意味着意外......无论您观察到的自动矢量化行为是否一致且可重复(可能它总是包裹在您的机器上......对于所有情况可能都不是这样)对于周围代码或所有编译器。或者如果编译器根据SSSE3,SSE4或AVX *的可用性选择不同的指令,如果它为不同的指令集做出不同的代码选择,那么可能甚至不是所有的处理器。 39; t利用签名溢出为UB)。
编辑:
好的,现在我们正在询问英特尔标准&#34; (它不存在,我认为你的意思是x86标准),我可以在我的答案中添加一些东西。事情有点令人费解。
首先,内部_mm_add_epi32由Microsoft定义,以匹配英特尔的内在函数API定义(https://software.intel.com/sites/landingpage/IntrinsicsGuide/和英特尔x86汇编手册中的内在注释)。他们巧妙地将它定义为x __m128i
指令对XMM寄存器执行PADDD
同样的事情,不再讨论(例如它是ARM上的编译错误还是应该模拟?)
其次,PADDD不仅仅是签名的补充!它是32位二进制加法。 x86对有符号整数使用两个补码,并且添加它们与无符号基数2的二进制操作相同。所以是的,paddd
保证包装。所有x86指令都有一个很好的参考here。
那么这是什么意思:再次,你的问题中的假设是有缺陷的,因为甚至没有任何溢出。所以你看到的输出应该是定义的行为。请注意,它由Microsoft和x86(不是C标准)定义。
其他x86编译器也以相同的方式实现了英特尔的内在函数API,因此_mm_add_epi32
可以保证只包装。
答案 1 :(得分:2)
__m128i
&#34;的字段内没有符号整数溢出。这是一个函数调用。 (作为编译器内在函数只是一种优化,就像内联一样,只要遵守as-if规则,就不会与C标准进行交互)
它的行为必须遵循函数开发人员记录的契约(前置条件,后置条件)。通常内在函数由编译器供应商记录,尽管它们倾向于协调内在函数的命名和契约以帮助移植代码。