有符号整数溢出、内部函数和未定义的行为



下面的非常简单的代码是否容易受到未定义行为的影响,因为整数由于操作而溢出?

static volatile LONG x = LONG_MAX;
InterlockedIncrement(&x);

根据该标准,有符号整数溢出是未定义的行为。但是,这里我们超出了标准,因为我们调用编译器的内在函数,该函数内联到某个汇编。此外,x的值不会在任何地方使用(该函数仅用作内存屏障)。

对类似问题的回答表明这不是 UB。

我声称这里没有UB,无论是根据语言标准(该标准不包括此功能/内部函数)还是根据实现,并且有一个简单的滚动。

这是我的推理...

InterlockedIncrement()在概念上非常简单,如果它有一个特殊情况,就很难错过它并且无法记录它。并且文档在这里没有提到任何特殊情况 大约 15+ 年。

无论如何,您将如何实现它?

如果您使用的是 80486 或更高版本,则最自然的实现使用带有LOCK前缀的XADD指令,该前缀以原子方式向内存变量添加值。指令本身不会生成任何溢出异常,但它确实会像常规加法指令一样修改EFLAGS寄存器,ADD,因此可以检测到溢出并对其采取行动。具体来说,您可以抛出INTO指令以将溢出条件转换为异常。或者,您可以使用条件跳转指令JO来跳转溢出处理程序。

如果您使用的是 80386 或更高版本,您还可以使用XCHG指令(此指令隐含LOCK),创建一个循环,尝试以原子方式更新内存变量(这就是 InterlockedExchange() 和 InterlockedCompareExchange() 的实现方式,自 80486 以来还有一个更方便的(为此目的)CMPXCHG指令)。在这种情况下,您需要像往常一样使用ADD指令或INC指令执行寄存器增量,并且您可以选择检测任何溢出情况(以EFLAGS.OF为单位)并如前所述进行处理。

现在,您想将INTOJO扔到所有InterlockedIncrement()实例中吗?可能不是,绝对不是默认的。人们喜欢他们的原子操作又小又快。

这是"即时"的UB。"爬行"的UB呢? 如果你有这样的 C 代码:

int a = INT_MAX;
if (a + 1 < a)
puts("Overflow!");

你现在可能什么也印不出来。 现代编译器知道a + 1不能合法地(!)溢出,因此无论a的值如何,if语句中的条件都可以被视为false。

你能对InterlockedIncrement()进行类似的优化吗?

好吧,鉴于变量是volatile的,并且确实可以随时在不同的线程中更改,编译器可能不会假设它的两次内存读取a不变(您可能会编写a + 1 < a或类似于多个语句,并且如果它是易失性的,则需要获取每个a)。

尝试进行优化也将是一个奇怪的上下文。

最新更新