c – 整数溢出未定义在内联x86程序集中?

前端之家收集整理的这篇文章主要介绍了c – 整数溢出未定义在内联x86程序集中?前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
说我有以下C代码
int32_t foo(int32_t x) {
    return x + 1;
}

当x == INT_MAX时,这是未定义的行为.现在说我用inline assembly代替了

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

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

解决方法

不,没有UB这个. C规则不适用于asm指令本身.只要内联asm语法包装指令,这是一个定义明确的语言扩展,它已经在支持它的实现上定义了行为.

有关这个问题的更通用版本,请参见Does undefined behavior apply to asm code?(与此相关的x86程序集和GNU C inline asm语言扩展).这里的答案主要集中在C方面,C和C标准中引用了标准对实现定义的语言扩展有多少要求.

另请参阅this comp.lang.c thread,了解是否有意义,说它有一般UB是有意义的,因为并不是所有的实现都有这样的扩展.

BTW,如果您只想在GNU C中使用定义的2的补全行为签名回卷,请编译为-fwrapv.不要使用inline asm. (或者使用__attribute__为只需要它的函数启用该选项.)wrapv与-fno-strict-overflow不完全相同,它只是基于假设程序没有UB来禁用优化;例如,在编译时常数计算中溢出只能使用-fwrapv安全.

内联asm行为是实现定义的,而GNU C inline asm is defined作为编译器的黑盒子.输入进入,输出出来,编译器不知道如何.所有它知道的是你告诉它使用out / in / clobber约束.

您使用内联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除非没有办法通过调整来哄骗编译器生成最优的asm )

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

一些体系结构在整数溢出中引发异常(陷阱),但x86不是其中之一(except when a division quotient doesn’t fit in the destination register).在MIPS上,如果您希望它们能够在没有陷阱的情况下进行换行,则可以使用ADDIU instead of ADDI的带符号整数. (因为它也是一个2的补码ISA,所以签名的环绕与二进制一样是无符号的环绕.)

未定义(或至少与实现相关)x86 asm中的行为:

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

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

可能任何给定的cpu具有一致的行为,但重要的是,即使它们仍然是x86,其他cpu可能会有所不同.如果您是微软,英特尔将会设计未来的cpu,以免破坏您现有的代码.如果您的代码是广泛依赖的,那么最好只坚持使用手册中记录的行为,而不仅仅是您的cpu发生的事情.见Andy Glew’s answer and comments here. Andy是英特尔P6微架构的架构师之一.

这些例子与C中的UB不一样.它们更像C将被称为“实现定义”,因为我们只是谈论一个未指定的值,而不是nasal demons的可能性(或者更合理的修改其他寄存器,或跳到某处).

对于真正未定义的行为,您可能需要查看特权指令,或至少是多线程代码.自修改代码在x86上也是潜在的UB:不能保证cpu“注意”存储到将要执行的地址,直到跳转指令为止.这是the question linked above主题(答案是:x86的真正实现超越了x86 ISA手册需要的,支持依赖于它的代码,并且因为窥探一直比高性能比冲洗更好跳跃.)

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

猜你在找的C&C++相关文章