我在向无符号位置添加有符号偏移时尝试检测溢出
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;
答案 0 :(得分:1)
您的测试是正确的。我现在看不到更优雅的方式,也许没有。
为什么条件是正确的:uint32_t
上的算术是算术模2 ^ 32。从int32_t
到uint32_t
的转换通常是对位模式的重新解释(无论如何,正如@caf指出的那样,这里的减少模2 ^ 32,所以它肯定有效)。将position
和offset
视为任意精度整数。当且仅当发生溢出时才会发生溢出
position + offset >= 2^32
。但offset < 2^31
,position + offset < position + 2^31
,小于position + 2^32
,下一个值减少到position
模2 ^ 32,uint32_t
,然后{ {1}}。另一方面,如果position + offset < position
和offset > 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 integers
和7.20.6.1 The abs, labs and llabs functions
部分),但实际上这种情况从未发生过,因为在大多数平台上,否定被实现为一个简单的指令(或一些),不会引发任何异常/中断/ CPU中的陷阱/事件,并且在生成额外的代码以检查此边缘情况时几乎没有价值,特别是因为整数用2的补码表示,并且-0x80000000的绝对值无论如何都是0x80000000,这可能很方便(例如,对于绝对值计算)。 CPU并不关心有符号整数,甚至对两者都使用相同的加法和减法指令(这是2的补码的好处),它很少关心整数溢出,因为它们通常在软件中发生并且是一种方式生活。要注意它,但不要冒汗。
请参阅Microsoft的 SafeInt for C ++(Code,Intro,On MSDN,Video)和 C(Intro + Code,On MSDN)的IntSafe 。