以下非常简单的代码是否容易受到未定义的行为的影响,因为整数因操作而溢出?
static volatile LONG x = LONG_MAX;
InterlockedIncrement(&x);
根据标准,有符号整数溢出是未定义的行为。但是,这里我们不符合标准,因为我们正在调用编译器的内部函数,它内联到某个程序集。此外,x
的值不在任何地方使用(该函数仅用作内存屏障)。
answer to a similar question表示这不是UB。
我声称这里没有UB,既没有语言标准(标准也没有涵盖这个功能/内在),也没有实现,并且有一个简单的翻转。
这是我的推理......
InterlockedIncrement()
在概念上非常简单,如果它有一个特殊情况,很难错过它并且不能记录它。并且文档在这里已经有15年以上没有提到任何特殊情况了。
你怎么会实现它?
如果您使用的是80486或更高版本,则最自然的实现使用带有XADD
前缀的LOCK
指令,该前缀以原子方式将值添加到内存变量中。该指令本身不会产生任何溢出异常,但它会像常规加法指令EFLAGS
那样修改ADD
寄存器,因此可以检测溢出并对其进行操作。具体来说,您可以抛出INTO
指令将溢出条件转换为异常。或者你可以使用条件跳转指令JO
来跳转溢出处理程序。
如果您使用的是80386或更高版本,您还可以使用XCHG
指令(LOCK
隐含在此指令中),以创建一个尝试以原子方式更新内存变量的循环(这是InterlockedExchange()和InterlockedCompareExchange(如何) )可以实现,自80486以来,还有一个更方便的(为此目的)CMPXCHG
指令。在这种情况下,您需要像往常一样使用ADD
指令或INC
指令执行寄存器增量,并且您可以选择检测任何溢出条件(在EFLAGS.OF
中)并按前面所述处理它。
现在,你想把INTO
或JO
扔进所有的InterlockedIncrement()
吗?可能不是,绝对不是默认的。人们喜欢他们的原子操作小而快。
这是“直接”的UB。那个“爬行”的UB怎么样?如果您有这样的C代码:
int a = INT_MAX;
if (a + 1 < a)
puts("Overflow!");
你现在可能会得到nothing printed。现代编译器知道a + 1
不能合法地(!)溢出,所以if语句中的条件可以被视为假,而不管a
的值。
你能和InterlockedIncrement()
进行类似的优化吗?
好吧,鉴于变量是volatile
并且确实可以在任何时刻在不同的线程中进行更改,编译器可能不会假设a
来自它的两个内存读取(您可能会编写a + 1 < a
或类似的多个语句,并且每个a
都需要如果它是不稳定的,则被取出)。
尝试进行优化也是一个奇怪的背景。