如何判断有符号数加无符号数时是否会溢出

9

我试图在将有符号偏移量添加到无符号位置时检测溢出。

uint32 position;
int32 offset;  // it could be negative
uint32 position = position+offset;

如何检查结果是否溢出或下溢?

我想到了一种丑陋的方法,但不确定其正确性。

  • 下溢: offset < 0 && position + offset >= position
  • 溢出: offset > 0 && position + offset <= position

我还想知道是否有更优雅的方法来解决这个问题。

更新:

如果offset是长整型,最好的解决方案是什么?

uint32 position;
long offset;  // it could be negative
uint32 position = position+offset;
3个回答

1
以下函数用于检查将int32_t加到uint32_t时是否会溢出/下溢。它还包含一些测试用例作为正确性的证明。
#include <stdint.h>
#include <assert.h>

int is_overflow (uint32_t position, int32_t offset)
{
    if (offset > 0 && offset > UINT32_MAX - position) {
        // really we checked (offset + position > UINT32_MAX)
        // overflow
        return 1;
    }
    else if (offset < 0 && (uint32_t)offset <= UINT32_MAX - position) {
        // really we checked  (position + (uint32_t)offset <= UINT32_MAX)
        // the (uint32_t)offset maps negative offset to [2^31, UINT32_MAX]
        // underflow
        return -1;
    }

    // no over/underflow
    return 0;
}

uint32_t abs_of_negative_int32 (int32_t offset)
{
    assert(offset < 0);

    return ((UINT32_MAX - (uint32_t)offset) + 1);
}

int main (int argc, char *argv[])
{
    int r;

    r = is_overflow(0, 0);
    assert(r == 0);

    r = is_overflow(0, 1);
    assert(r == 0);

    r = is_overflow(0, INT32_MAX - 1);
    assert(r == 0);

    r = is_overflow(0, INT32_MAX);
    assert(r == 0);

    r = is_overflow(0, -1);
    assert(r == -1);

    r = is_overflow(0, INT32_MIN + 1);
    assert(r == -1);

    r = is_overflow(0, INT32_MIN);
    assert(r == -1);

    r = is_overflow(UINT32_MAX, 0);
    assert(r == 0);

    r = is_overflow(UINT32_MAX, 1);
    assert(r == 1);

    r = is_overflow(UINT32_MAX - 1, 1);
    assert(r == 0);

    r = is_overflow(UINT32_MAX - 1, 2);
    assert(r == 1);

    r = is_overflow(UINT32_MAX - 1, INT32_MAX);
    assert(r == 1);

    r = is_overflow(UINT32_MAX - INT32_MAX, INT32_MAX);
    assert(r == 0);

    r = is_overflow(UINT32_MAX - INT32_MAX + 1, INT32_MAX);
    assert(r == 1);

    r = is_overflow(abs_of_negative_int32(INT32_MIN), INT32_MIN);
    assert(r == 0);

    r = is_overflow(abs_of_negative_int32(INT32_MIN) - 1, INT32_MIN);
    assert(r == -1);

    return 0;
}

很好的解决方案,用于is_overflow()/abs_of_negative_int32()等UB问题以及测试代码。 - Alexey Frunze

1

你的测试是正确的。我现在没有看到更优雅的方法,也许没有。

为什么条件是正确的:在uint32_t上进行算术运算相当于对2^32取模。从int32_t转换为uint32_t通常是位模式的重新解释(无论如何,正如@caf所指出的,在这里它是对2^32取模的减少,因此它绝对有效)。将positionoffset视为任意精度整数。只有当
position + offset >= 2^32时才会发生溢出。但是offset < 2^31,因此position + offset < position + 2^31,小于position + 2^32,下一个将被减少到position模2^32的值,因此作为uint32_t,则position + offset < position。另一方面,如果offset > 0position + offset < position,显然已经发生了溢出。只有当数学整数position + offset < 0时才会发生下溢。由于offset >= -2^31,类似的推理表明,只有当offset < 0 && position + offset > position时才会发生下溢。

2
将负数转换为无符号数不是实现定义的 - 它被减少模比无符号类型中可表示的最大值大一的数字。这是标准保证的。 - caf
哦,好的,我记错了。谢谢你提醒我。 - Daniel Fischer
@DanielFischer,当偏移量加到位置时,它将被转换为uint32_t。因此实际上偏移量<=2^32-1。感谢您的解释。 - rogerz
在那部分中,你应该将它们视为数学整数。不够清晰 :-( - Daniel Fischer
@DanielFischer 您是正确的。它在偏移量>0的分支中。 - rogerz
我认为原帖作者的代码不具备可移植性。在int为64位的机器上,position+offset将使用64位有符号int类型进行评估,而不是uint32_t - supercat

0

以下是如何实现的:

uint32 addui(uint32 position, int32 offset, int* overflow)
{
  *overflow = (((offset >= 0) && (0xFFFFFFFFu - position < (uint32)offset)) ||
               ((offset < 0) && (position < (uint32)-offset)));
  return position + offset;
}

u后缀是为了确保0xFFFFFFFF常量是无符号类型(没有后缀的十六进制常量可以是有符号或无符号的,具体取决于值以及编译器如何定义int、long和long long),因此左侧的表达式是无符号的。它可能不是必需的,但我有点累了,不想去弄清楚它是否不需要。这样做肯定不会有坏处。

(uint32)转换是为了让编译器闭嘴,因为它可能认为我们在比较有符号和无符号的东西。

更新:如果int32具有2的补码表示,且offset = -0x80000000,则表达式-offset允许根据C标准引发实现定义信号,甚至可能导致未定义的行为(请参阅C99的6.3.1.3有符号和无符号整数7.20.6.1 abs、labs和llabs函数部分),但实际上几乎从不会发生,因为在大多数平台上,否定是以简单的指令(或少数)实现的,并且CPU中不会引发任何异常/中断/陷阱/事件,生成额外的代码来检查这种边缘情况几乎没有价值,特别是因为整数使用2的补码表示,而-0x80000000的绝对值始终为0x80000000,这可能很方便(例如,用于绝对值计算)。 CPU对有符号整数并不太关心,甚至对两者都使用相同的加法和减法指令(这是2的补码的好处),并且它很少关心整数溢出,因为它们在软件中经常发生,并且是一种生活方式。请注意这一点,但不要过度担心。
请查看Microsoft的C++中的SafeInt(代码, 介绍, MSDN上, 视频)和C中的IntSafe(介绍+代码, MSDN上),了解它们是如何实现的。

这可能存在与Alex的答案相同的问题,即当offset==INT32_MIN时,-offset可能未定义。它可能在某些特定实现中工作,但不是普遍适用的。 - Ambroz Bizjak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接