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

And*_*Sun 10 c x86 gcc undefined-behavior

说我有以下C代码:

int32_t foo(int32_t x) {
    return x + 1;
}
Run Code Online (Sandbox Code Playgroud)

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

int32_t foo(int32_t x) {
    asm("incl %0" : "+g"(x));
    return x;
}
Run Code Online (Sandbox Code Playgroud)

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

Pet*_*des 13

,这没有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约束告诉它的内容.


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

int32_t foo(int32_t x) {
    uint32_t u = x;
    return ++u;
}
Run Code Online (Sandbox Code Playgroud)

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

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

某些体系结构确实在整数溢出时引发异常(陷阱),但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所谓的"实现定义",因为我们只讨论一个未指定的值,而不是鼻子恶魔的可能性.(或者更合理地修改其他寄存器,或跳到某处).

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

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