当前位置: 首页 > 知识库问答 >
问题:

内联x86程序集中的整数溢出未定义吗?

宰父远
2023-03-14

假设我有以下 C 代码:

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

当< code>x == INT_MAX时,这是未定义的行为。现在假设我用内联汇编执行了加法:

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

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

共有2个答案

须巴英
2023-03-14

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

您使用的是稍有不同的语言“C with x86 32位内联汇编程序”。您生成了有效的汇编语句。这种行为大概是由英特尔的参考手册定义的。在这里,整数加1到INT_MAX的行为得到了很好的定义。它的定义方式不会干扰C程序的执行。

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

梁丘高朗
2023-03-14

不,这里没有UB。C规则不适用于ASM指令本身。就封装指令的内联ASM语法而言,这是一个定义良好的语言扩展,它定义了支持它的实现的行为。

请参阅未定义的行为是否适用于asm代码?对于这个问题的更通用版本(与这个关于x86汇编和GNUC内联asm语言扩展的版本相比)。那里的答案集中在C方面,引用了C和C标准,记录了标准对语言的实现定义扩展所说的话是多么少。

另请参见comp.lang.c线程,讨论说它有UB“一般”是否有意义,因为不是所有的实现都有那个扩展。

顺便说一句,如果你只是想在GNU C中使用定义的2补码行为进行有符号环绕,请使用-fwrapv编译。不要使用内联的ASM。(或者使用__attribute__为需要它的函数启用该选项。)wrapv-fno-stry-overflow不完全一样,它只是在假设程序没有任何UB的基础上禁用优化;例如,编译时常量计算中的溢出只有在-fwrapv下才是安全的。

内联 asm 行为是实现定义的,GNU C 内联 asm 被定义为编译器的黑匣子。输入进入,输出出来,编译器不知道如何操作。它所知道的只是你使用出/入/氯伯约束告诉它什么。

您的foo使用inline-ash的行为与

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。)

它不会引发异常。设置OF标志对任何事情都没有影响,因为用于x86 (i386和amd64)的GNU C inline asm有一个隐式的< code >“cc” clobber,所以编译器将假定EFLAGS中的条件代码在每个inline-asm语句后都保存垃圾。gcc6为asm引入了一种新的语法来产生标志结果(它可以在asm中保存一个SETCC,并为希望返回标志条件的asm块保存一个由编译器生成的测试)。

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

如果输入为零,BSF和BSR(向前或向后查找第一个设置位)用未定义的内容离开它们的目的寄存器。(TZCNT和LZCNT没有那个问题)。英特尔最近的x86 CPUs确实定义了行为,即不修改目标,但x86手册并不保证这一点。请参阅本答案中关于TZCNT的部分,了解更多关于其含义的讨论,例如TZCNT/LZCNT/POPCNT对英特尔CPU的输出有错误的依赖性。

其他几个指令在某些/所有情况下都会留下一些未定义的标志。(尤其是自动对焦/自动对焦)。例如,IMUL 未定义采埃孚、PF 和 AF。

想必任何给定的CPU都有一致的行为,但关键是其他CPU的行为可能会有所不同,即使它们仍然是x86。如果您是Microsoft,Intel将设计其未来的CPU,使其不会破坏您现有的代码。如果你的代码被广泛依赖,你最好坚持只依赖手册中记录的行为,而不仅仅是你的CPU所做的。请在此查看Andy Glew的回答和评论。Andy是Intel P6微体系结构的架构师之一。

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

对于真正未定义的行为,您可能需要查看特权指令,或者至少多线程代码。自修改代码在x86上也可能是UB:不能保证CPU“注意到”将要执行的地址存储,直到跳转指令之后。这是上面链接的问题的主题(答案是:x86的真正实现超出了x86 ISA手册的要求,以支持依赖于它的代码,并且因为一直窥探比跳转时刷新更有利于高性能。)

汇编语言中未定义的行为非常罕见,尤其是如果你不考虑某个特定值未被指定,但“损害”的范围是可预测的和有限的情况。

 类似资料:
  • 众所周知,有符号整数溢出是一种未定义的行为。但是在C 11文档中有一些有趣的东西: 带符号整数类型,宽度分别为8、16、32和64位,不带填充位,并使用2的补码表示负值(仅在实现直接支持该类型时提供) 参见链接 我的问题是:既然标准明确规定,、、和负数是2的补码,那么这些类型的溢出仍然是一种未定义的行为吗? 编辑我检查了C 11和C11标准,以下是我的发现: C 11,§18.4.1: 标题定义了

  • 最近,有符号整数溢出在C和C中没有正式定义,这引起了很多关注。然而,给定的实现可能会选择定义它;在C语言中,实现可以设置

  • 虚拟机安装:Ubuntu 12.04(x86) 什么是整数溢出? 存储大于最大支持值的值称为整数溢出。整数溢出本身不会导致任意代码执行,但整数溢出可能会导致堆栈溢出或堆溢出,这可能导致任意代码执行。在这篇文章中,我将仅谈论整数溢出导致堆栈溢出,整数溢出导致堆溢出将在后面的单独的帖子中讨论。 数据类型大小及范围: 当我们试图存储一个大于最大支持值的值时,我们的值会被包装 。例如,当我们尝试将存储到带

  • 我在一次采访中被问及这一点。我被要求计算数字x1,x2,x3,…的平均值,。。。xn公司 //所以归结起来是这样的: 面试官说列表的大小是未知的,它可能很大,所以总和可能会溢出。他问我如何解决溢出问题,我的回答是跟踪我们可能超过最大数量的次数等等,他说了一些关于推入堆栈、平均值和长度的事情,我从来没有真正理解他的解决方案,将这两个变量推入某种列表中?有人知道吗?

  • 这是一个用于x86处理器的简单dos汇编程序。这是一个简单的helloworld程序。 我不明白的是图像中下面的连续内存地址。程序似乎从十六进制中的地址0100开始,即256。下一个地址是258。差异似乎是2个字节。是不是这样指令(操作码地址)是2个字节? 然后再往下-mov dx指令似乎占用3个字节(0117-011A),而mov ah指令占用2个字节。 我以为指令(操作码地址)应该在内存中占用

  • 这是一个例子来说明我的问题,其中涉及一些更复杂的代码,我不能在这里张贴。 这个程序在我的平台上包含未定义的行为,因为会在第三个循环上溢出。 这会使整个程序有未定义的行为,还是只有在溢出真正发生之后?编译器可能会发现会溢出,这样它就可以声明整个循环未定义,并且不用费心运行printfs,即使它们都发生在溢出之前? (标记的C和C虽然不同,但我对这两种语言的答案感兴趣,如果它们不同的话。)