添加签名到无符号时如何判断溢出

时间:2011-11-03 09:18:48

标签: c overflow unsigned signed underflow

我在向无符号位置添加有符号偏移时尝试检测溢出

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

如何检查结果是溢出还是下溢?

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

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

我也想知道是否有更优雅的方式来做这件事。

更新

如果偏移很长,最佳解决方案是什么?

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

3 个答案:

答案 0 :(得分:1)

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

为什么条件是正确的:uint32_t上的算术是算术模2 ^ 32。从int32_tuint32_t的转换通常是对位模式的重新解释(无论如何,正如@caf指出的那样,这里的减少模2 ^ 32,所以它肯定有效)。将positionoffset视为任意精度整数。当且仅当发生溢出时才会发生溢出 position + offset >= 2^32。但offset < 2^31position + offset < position + 2^31,小于position + 2^32,下一个值减少到position模2 ^ 32,uint32_t,然后{ {1}}。另一方面,如果position + offset < positionoffset > 0,则显然已发生溢出。当且仅当position + offset < position为数学整数时才会发生下溢。自position + offset < 0以来,类似的推理显示当且仅当offset >= -2^31发生了下溢。

答案 1 :(得分: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;
}

答案 2 :(得分: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),因此表达式为左of&lt;没有签名。它可能不需要,但我有点累,弄清楚它是不是。它肯定不会受到伤害。

(uint32)强制转换是为了关闭可能认为我们正在做一些愚蠢事情的编译器(比较使用unsigned签名)。

UPDATE :如果int32具有2的补码表示且offset = -0x80000000,则允许表达式-offset引发实现定义的信号,甚至可能导致每个C的未定义行为标准(参见C99的6.3.1.3 Signed and unsigned integers7.20.6.1 The abs, labs and llabs functions部分),但实际上这种情况从未发生过,因为在大多数平台上,否定被实现为一个简单的指令(或一些),不会引发任何异常/中断/ CPU中的陷阱/事件,并且在生成额外的代码以检查此边缘情况时几乎没有价值,特别是因为整数用2的补码表示,并且-0x80000000的绝对值无论如何都是0x80000000,这可能很方便(例如,对于绝对值计算)。 CPU并不关心有符号整数,甚至对两者都使用相同的加法和减法指令(这是2的补码的好处),它很少关心整数溢出,因为它们通常在软件中发生并且是一种方式生活。要注意它,但不要冒汗。

请参阅Microsoft的 SafeInt for C ++(CodeIntroOn MSDNVideo)和 C(Intro + CodeOn MSDN)的IntSafe