内联x86程序集中是否未定义整数溢出?

说我有以下C代码:

int32_t foo(int32_t x) { return x + 1; } 

x == INT_MAX时,这是未定义的行为。 现在说我用内联汇编代替了:

 int32_t foo(int32_t x) { asm("incl %0" : "+g"(x)); return x; } 

问题:当x == INT_MAX时,内联汇编版本是否仍会调用未定义的行为? 或者未定义的行为仅适用于C代码?

,这没有UB。 C规则不适用于asm指令本身。 至于包含指令的inline-asm语法,这是一个明确定义的语言扩展,它在支持它的实现上定义了行为。

请参阅未定义的行为是否适用于asm代码? 对于这个问题的更通用版本(与此关于x86程序集和GNU C内联asm语言扩展)。 那里的答案集中在事物的C方面,引用了C和C ++标准,这些标准记录了标准对语言的实现定义扩展的说法。

另请参阅此comp.lang.c线程 ,了解是否有理由说它具有UB“一般”,因为并非所有实现都具有该扩展。


顺便说一句, 如果您只想在GNU C中使用已定义的2的补码行为进行签名环绕,请使用-fwrapv编译 。 不要使用内联asm。 (或者使用__attribute__为只需要它的函数启用该选项。) wrapv-fno-strict-overflow不完全相同,它只是基于假设程序没有任何UB而禁用优化; 例如,编译时常量计算中的溢出仅对-fwrapv是安全的。


Inline-asm行为是实现定义的, GNU C inline asm被定义为编译器的黑盒子。 输入进入,输出出来,编译器不知道如何。 它只知道你使用out / in / clobber约束告诉它的东西。


使用inline-asm的foo行为完全相同

 int32_t foo(int32_t x) { uint32_t u = x; return ++u; } 

在x86上,因为x86是2的补码机器,所以整数环绕是明确定义的。 (性能除外:asm版本无法继续传播,也使编译器无法将x - inc(x)优化为-1等等.https://gcc.gnu.org/wiki/DontUseInlineAsm除非有无法通过调整C来诱导编译器生成最佳asm。)

它不会引发exception。 设置OF标志对任何事情都没有影响,因为x86(i386和amd64)的GNU C内联asm具有隐含的"cc" clobber,因此编译器将假设EFLAGS中的条件代码在每个inline-asm语句之后保留垃圾。 gcc6为asm引入了一种新的语法来生成标志结果(它可以在你的asm中保存一个SETCC,并为编译器为想要返回标志条件的asm块保存一个TEST)。

某些体系结构确实在整数溢出时引发exception(陷阱),但x86不是其中之一( 除非分割商不适合目标寄存器 )。 在MIPS上,如果您希望它们能够在没有陷印的情况下进行换行,则可以在有符号整数上使用ADDIU而不是ADDI 。 (因为它也是2的补码ISA,所以签名环绕二进制与无符号环绕相同。)


x86 asm中未定义(或至少依赖于实现)的行为:

如果输入为零,则BSF和BSR(找到第一个设置位正向或反向)将其目标寄存器保留为未定义的内容。 (TZCNT和LZCNT没有那个问题)。 英特尔最近的x86 CPU确实定义了行为,即保持目的地未经修改,但x86手册并不能保证这一点。 有关含义的更多讨论,请参阅本答案中有关TZCNT的部分,例如,TZCNT / LZCNT / POPCNT对Intel CPU中的输出具有错误依赖性。

其他一些指令在某些/所有情况下都会留下一些未定义的标志。 (尤其是AF / PF)。 IMUL例如留下ZF,PF和AF未定义。

据推测,任何给定的CPU都具有一致的行为,但重点是其他CPU可能表现不同,即使它们仍然是x86。 如果您是Microsoft,英特尔将设计未来的CPU,以免破坏现有代码。 如果您的代码是广泛依赖的,那么您最好只依赖于手册中记录的行为,而不仅仅是您的CPU发生的事情。 请参阅Andy Glew的回答和评论 。 Andy是英特尔P6微体系结构的架构师之一。

这些例子与C中的UB不同 。 它们更像是C所谓的“实现定义”,因为我们只讨论一个未指定的值,而不是鼻子恶魔的可能性。 (或者更合理地修改其他寄存器,或跳到某处)。

对于真正未定义的行为,您可能需要查看特权指令,或至少查看multithreading代码。 自修改代码在x86上也可能是UB:不能保证CPU“通知”存储到即将在跳转指令之后执行的地址。 这是上面链接的问题的主题(答案是:x86的实际实现超出了x86 ISA手册所要求的范围,支持依赖于它的代码,并且因为窥探所有时间对于高性能更好比冲洗更快。)

汇编语言中未定义的行为非常罕见,特别是如果您不计算未指定特定值但“损坏”范围可预测且有限的情况。

好吧,C标准没有定义内联汇编程序的function,因此根据C标准 ,任何内联汇编程序都是未定义的行为。

您正在使用略有不同的语言“C with x86 32 bit inline assembler”。 您生成了有效的汇编语句。 这种行为可能是由英特尔的参考手册定义的。 并且在INT_MAX中添加1的整数加法的行为已被很好地定义。 它的定义方式是它不会干扰C程序的执行。

试图通过空指针读取值的内联汇编程序也可以在汇编程序级别上很好地定义,但它的行为会干扰程序的执行(也就是崩溃)。