有符号整数溢出,内在函数和未定义行为

时间:2019-03-01 07:47:59

标签: c++ windows visual-studio undefined-behavior intrinsics

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

static volatile LONG x = LONG_MAX;

InterlockedIncrement(&x);

根据标准,带符号整数溢出是未定义的行为。但是,这里我们不符合标准,因为我们正在调用编译器的内在函数,该函数内联到某些程序集。另外,x的值不会在任何地方使用(该功能仅用作内存屏障)。

answer to a similar question提示这不是UB。

1 个答案:

答案 0 :(得分:2)

我声称这里既没有UB,也没有语言标准(该标准不涵盖此功能/本征)或实现,都没有UB,并且有一个简单的过渡。

这是我的理由...

InterlockedIncrement()从概念上讲非常简单,并且如果有特殊情况,将很难错过并且无法记录下来。而且文档在15年以上的时间里都没有提到任何特殊情况。

您将如何实施它?

如果您使用的是80486或更高版本,则最自然的实现方式是使用带有XADD前缀的LOCK指令,该指令原子地向内存变量添加一个值。该指令本身不会产生任何溢出异常,但是它会像常规加法指令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!");

如今您可能会获得nothing printed。 现代编译器知道a + 1不会合法地(!)溢出,因此if语句中的条件可以被视为false,而与a的值无关。

您可以对InterlockedIncrement()进行类似的优化吗?

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

尝试进行优化也很奇怪。