我正在用C++编写一个程序来查找 b = c 的所有解决方案,其中 a、b 和 c 一起使用所有数字 0-9 恰好一次。该程序循环访问 a 和 b 的值,并且每次在 a、b 和 b 上运行一个数字计数例程,以检查是否满足数字条件。
但是,当 b 溢出整数限制时,可能会生成虚假解。我最终使用以下代码检查了这一点:
unsigned long b, c, c_test;
...
c_test=c*b; // Possible overflow
if (c_test/b != c) {/* There has been an overflow*/}
else c=c_test; // No overflow
有没有更好的方法来测试溢出?我知道有些芯片有一个内部标志,在溢出发生时设置,但我从未见过通过 C 或 C++ 访问它。
请注意,签名int
溢出是 C 和 C++ 中未定义的行为,因此您必须在不实际导致它的情况下检测到它。对于添加前的有符号 int 溢出,请参阅 在 C/C++ 中检测签名溢出。
我看到你使用的是无符号整数。根据定义,在 C 中(我不知道C++(,无符号算术不会溢出......所以,至少对于 C 来说,你的观点是没有实际意义的:)
对于有符号整数,一旦溢出,就会发生未定义的行为 (UB(,并且程序可以执行任何操作(例如:使测试不确定(。
#include <limits.h>
int a = <something>;
int x = <something>;
a += x; /* UB */
if (a < 0) { /* Unreliable test */
/* ... */
}
要创建符合的程序,您需要在生成所述溢出之前测试溢出。该方法也可以与无符号整数一起使用:
// For addition
#include <limits.h>
int a = <something>;
int x = <something>;
if (x > 0 && a > INT_MAX - x) // `a + x` would overflow
if (x < 0 && a < INT_MIN - x) // `a + x` would underflow
<小时 /> // For subtraction
#include <limits.h>
int a = <something>;
int x = <something>;
if (x < 0 && a > INT_MAX + x) // `a - x` would overflow
if (x > 0 && a < INT_MIN + x) // `a - x` would underflow
<小时 /> // For multiplication
#include <limits.h>
int a = <something>;
int x = <something>;
// There may be a need to check for -1 for two's complement machines.
// If one number is -1 and another is INT_MIN, multiplying them we get abs(INT_MIN) which is 1 higher than INT_MAX
if (a == -1 && x == INT_MIN) // `a * x` can overflow
if (x == -1 && a == INT_MIN) // `a * x` (or `a / x`) can overflow
// general case
if (x != 0 && a > INT_MAX / x) // `a * x` would overflow
if (x != 0 && a < INT_MIN / x) // `a * x` would underflow
<小时 />对于除法(INT_MIN
和-1
特殊情况除外(,不可能超过INT_MIN
或INT_MAX
。
从 C23 开始,标准标头<stdckdint.h>
提供以下三个类似函数的宏:
bool ckd_add(type1 *result, type2 a, type3 b);
bool ckd_sub(type1 *result, type2 a, type3 b);
bool ckd_mul(type1 *result, type2 a, type3 b);
其中 type1
、 type2
和 type3
是任何整数类型。这些函数分别以任意精度加、减或乘 a 和 b,并将结果存储在 *result
中。如果结果不能用type1
精确地表示,则函数返回true
("计算已溢出"(。(任意精度是一种错觉;计算速度非常快,自1990年代初以来,几乎所有可用的硬件都可以在一两个指令中完成。
重写OP的示例:
unsigned long b, c, c_test;
// ...
if (ckd_mul(&c_test, c, b))
{
// returned non-zero: there has been an overflow
}
else
{
c = c_test; // returned 0: no overflow
}
c_test 包含所有情况下乘法的潜在溢出结果。
早在 C23 之前,GCC 5+ 和 Clang 3.8+ 就提供了以相同方式工作的内置函数,只是结果指针最后传递而不是第一个传递:__builtin_add_overflow
、__builtin_sub_overflow
和 __builtin_mul_overflow
。这些也适用于小于 int
的类型。
unsigned long b, c, c_test;
// ...
if (__builtin_mul_overflow(c, b, &c_test))
{
// returned non-zero: there has been an overflow
}
else
{
c = c_test; // returned 0: no overflow
}
Clang 3.4+ 引入了固定类型的算术溢出内置函数,但它们的灵活性要差得多,Clang 3.8 已经存在了很长时间。尽管有更方便的新选择,但如果您需要使用它,请寻找__builtin_umull_overflow
。
Visual Studio 的 cl.exe 没有直接的等价物。对于无符号的加法和减法,包括<intrin.h>
将允许您使用addcarry_uNN
和subborrow_uNN
(其中 NN 是位数,如 addcarry_u8
或 subborrow_u64
(。他们的签名有点迟钝:
unsigned char _addcarry_u32(unsigned char c_in, unsigned int src1, unsigned int src2, unsigned int *sum);
unsigned char _subborrow_u32(unsigned char b_in, unsigned int src1, unsigned int src2, unsigned int *diff);
c_in
/b_in
是输入时的进位/借用标志,返回值是输出时的进位/借用。它似乎没有符号运算或乘法的等价物。
否则,Clang适用于Windows现在已经做好了生产准备(对于Chrome来说已经足够好了(,所以这也是一种选择。
有一种方法可以确定操作是否可能溢出,使用操作数中最重要的一位的位置和一些基本的二进制数学知识。
此外,任何两个操作数将导致(最多(比最大操作数的最高一位多一位。例如:
bool addition_is_safe(uint32_t a, uint32_t b) {
size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
return (a_bits<32 && b_bits<32);
}
对于乘法,任何两个操作数将(最多(产生操作数位的总和。例如:
bool multiplication_is_safe(uint32_t a, uint32_t b) {
size_t a_bits=highestOneBitPosition(a), b_bits=highestOneBitPosition(b);
return (a_bits+b_bits<=32);
}
同样,您可以估计结果的最大大小 a
的 b
的幂,如下所示:
bool exponentiation_is_safe(uint32_t a, uint32_t b) {
size_t a_bits=highestOneBitPosition(a);
return (a_bits*b<=32);
}
(当然,用位数代替目标整数。
我不确定确定数字中最高一位位置的最快方法,这是一个蛮力方法:
size_t highestOneBitPosition(uint32_t a) {
size_t bits=0;
while (a!=0) {
++bits;
a>>=1;
};
return bits;
}
它并不完美,但这可以让您在进行操作之前很好地了解任何两个数字是否会溢出。我不知道它是否比简单地按照您建议的方式检查结果更快,因为 highestOneBitPosition
函数中的循环,但它可能会(特别是如果您事先知道操作数中有多少位(。
一些编译器提供对 CPU 中整数溢出标志的访问,然后您可以对其进行测试,但这不是标准的。
您还可以在执行乘法之前测试溢出的可能性:
if ( b > ULONG_MAX / a ) // a * b would overflow
警告:GCC 在使用 -O2
编译时可以优化溢出检查。在某些情况下,该选项-Wall
会给您警告,例如
if (a + b < a) { /* Deal with overflow */ }
但不是在这个例子中:
b = abs(a);
if (b < 0) { /* Deal with overflow */ }
唯一安全的方法是在溢出发生之前检查溢出,如 CERT 论文中所述,系统地使用这将非常乏味。
使用 -fwrapv
进行编译可以解决问题,但会禁用某些优化。
我们迫切需要一个更好的解决方案。我认为编译器在进行依赖于未发生溢出的优化时应默认发出警告。目前的情况允许编译器优化溢出检查,这在我看来是不可接受的。
Clang 现在支持对有符号和无符号整数进行动态溢出检查。请参阅 -fsanitize=integer 开关。目前,它是唯一具有完全支持动态溢出检查以进行调试的C++编译器。
我看到很多人回答了关于溢出的问题,但我想解决他最初的问题。他说,问题是找到一个b=c,这样所有数字都可以使用而不重复。好吧,这不是他在这篇文章中提出的,但我仍然认为有必要研究问题的上限并得出结论,他永远不需要计算或检测溢出(注意:我不精通数学,所以我一步一步地做了这个,但最终结果是如此简单,这可能有一个简单的公式(。
重点是问题对 a、b 或 c 要求的上限是 98.765.432。无论如何,首先将问题分为琐碎和非琐碎部分:
- x0 == 1(9、8、7、6、5、4、3、2 的所有排列都是解决方案( x
- 1 == x (无法解决(
- 0b == 0(无法解决(
- 1b == 1(无法解决( a b、a> 1
- 、b> 1(非平凡(
现在我们只需要证明没有其他解决方案是可能的,只有排列是有效的(然后打印它们的代码是微不足道的(。我们回到上限。实际上上限是 c ≤ 98.765.432。它是上限,因为它是 8 位数字的最大数字(a 和 b 总共 10 位数字减去 1(。这个上限仅适用于 c,因为 a 和 b 的边界必须低得多,因为指数增长,我们可以计算,将 b 从 2 变为上限:
9938.08^2 == 98765432
462.241^3 == 98765432
99.6899^4 == 98765432
39.7119^5 == 98765432
21.4998^6 == 98765432
13.8703^7 == 98765432
9.98448^8 == 98765432
7.73196^9 == 98765432
6.30174^10 == 98765432
5.33068^11 == 98765432
4.63679^12 == 98765432
4.12069^13 == 98765432
3.72429^14 == 98765432
3.41172^15 == 98765432
3.15982^16 == 98765432
2.95305^17 == 98765432
2.78064^18 == 98765432
2.63493^19 == 98765432
2.51033^20 == 98765432
2.40268^21 == 98765432
2.30883^22 == 98765432
2.22634^23 == 98765432
2.15332^24 == 98765432
2.08826^25 == 98765432
2.02995^26 == 98765432
1.97741^27 == 98765432
注意,例如最后一行:它说 1.97^27 ~98M。因此,例如,1^27 == 1 和 2^27 == 134.217.728,这不是解决方案,因为它有 9 位数字(2> 1.97,所以它实际上比应该测试的要大(。可以看出,可用于测试 a 和 b 的组合非常小。对于 b == 14,我们需要尝试 2 和 3。对于 b == 3,我们从 2 开始,在 462 处停止。所有结果均小于~98M。
现在只需测试上面的所有组合并查找不重复任何数字的组合:
['0', '2', '4', '5', '6', '7', '8'] 84^2 = 7056
['1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481
['0', '1', '2', '3', '4', '5', '8', '9'] 59^2 = 3481 (+leading zero)
['1', '2', '3', '5', '8'] 8^3 = 512
['0', '1', '2', '3', '5', '8'] 8^3 = 512 (+leading zero)
['1', '2', '4', '6'] 4^2 = 16
['0', '1', '2', '4', '6'] 4^2 = 16 (+leading zero)
['1', '2', '4', '6'] 2^4 = 16
['0', '1', '2', '4', '6'] 2^4 = 16 (+leading zero)
['1', '2', '8', '9'] 9^2 = 81
['0', '1', '2', '8', '9'] 9^2 = 81 (+leading zero)
['1', '3', '4', '8'] 3^4 = 81
['0', '1', '3', '4', '8'] 3^4 = 81 (+leading zero)
['2', '3', '6', '7', '9'] 3^6 = 729
['0', '2', '3', '6', '7', '9'] 3^6 = 729 (+leading zero)
['2', '3', '8'] 2^3 = 8
['0', '2', '3', '8'] 2^3 = 8 (+leading zero)
['2', '3', '9'] 3^2 = 9
['0', '2', '3', '9'] 3^2 = 9 (+leading zero)
['2', '4', '6', '8'] 8^2 = 64
['0', '2', '4', '6', '8'] 8^2 = 64 (+leading zero)
['2', '4', '7', '9'] 7^2 = 49
['0', '2', '4', '7', '9'] 7^2 = 49 (+leading zero)
它们都与问题不匹配(这也可以从缺少"0"、"1"、"..."、"9"中看到(。
解决该问题的示例代码如下。另请注意,这是用 Python 编写的,不是因为它需要任意精度的整数(代码不会计算大于 9800 万的任何内容(,而是因为我们发现测试量如此之小,以至于我们应该使用高级语言来利用其内置的容器和库(另请注意:代码有 28 行(。
import math
m = 98765432
l = []
for i in xrange(2, 98765432):
inv = 1.0/i
r = m**inv
if (r < 2.0): break
top = int(math.floor(r))
assert(top <= m)
for j in xrange(2, top+1):
s = str(i) + str(j) + str(j**i)
l.append((sorted(s), i, j, j**i))
assert(j**i <= m)
l.sort()
for s, i, j, ji in l:
assert(ji <= m)
ss = sorted(set(s))
if s == ss:
print '%s %d^%d = %d' % (s, i, j, ji)
# Try with non significant zero somewhere
s = ['0'] + s
ss = sorted(set(s))
if s == ss:
print '%s %d^%d = %d (+leading zero)' % (s, i, j, ji)
这是一种非常快速的方法来检测至少加法的溢出,这可能会为乘法、除法和幂提供线索。
这个想法是,正是因为处理器只是让值包装回零,并且 C/C++ 是从任何特定处理器中抽象出来的,所以您可以:
uint32_t x, y;
uint32_t value = x + y;
bool overflow = value < (x | y);
这既保证了如果一个操作数为零而另一个不是,则溢出不会被错误地检测到,并且比之前建议的许多 NOT/XOR/AND/TEST 操作要快得多。
正如所指出的,这种方法虽然比其他更复杂的方法更好,但仍然是可以优化的。以下是包含优化的原始代码的修订版:
uint32_t x, y;
uint32_t value = x + y;
const bool overflow = value < x; // Alternatively "value < y" should also work
检测乘法溢出的一种更有效、更便宜的方法是:
uint32_t x, y;
const uint32_t a = (x >> 16U) * (y & 0xFFFFU);
const uint32_t b = (x & 0xFFFFU) * (y >> 16U);
const bool overflow = ((x >> 16U) * (y >> 16U)) +
(a >> 16U) + (b >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;
这会导致溢出时UINT32_MAX,或乘法的结果。在这种情况下,允许对有符号整数进行乘法是严格未定义的行为。
值得注意的是,这使用部分 Karatsuba 方法乘法分解来计算 64 位乘法的高 32 位,以检查它们中的任何一个是否应该设置为知道 32 位乘法是否溢出。
如果使用C++,您可以将其转换为一个整洁的小λ来计算溢出,以便隐藏检测器的内部工作原理:
uint32_t x, y;
const bool overflow
{
[](const uint32_t x, const uint32_t y) noexcept -> bool
{
const uint32_t a{(x >> 16U) * uint16_t(y)};
const uint32_t b{uint16_t(x) * (y >> 16U)};
return ((x >> 16U) * (y >> 16U)) + (a >> 16U) + (b >> 16U);
}(x, y)
};
uint32_t value{overflow ? UINT32_MAX : x * y};
这是该问题的"非便携式"解决方案。英特尔x86和x64 CPU具有所谓的EFLAGS寄存器,该寄存器由处理器在每次整数算术运算后填充。我将在这里跳过详细的描述。相关标志是"溢出"标志(掩码0x800(和"携带"标志(掩码0x1(。为了正确解释它们,应考虑操作数是有符号类型还是无符号类型。
这是检查 C/C++ 标志的实用方法。以下代码适用于 Visual Studio 2005 或更高版本(32 位和 64 位(以及 GNU C/C++ 64 位。
#include <cstddef>
#if defined( _MSC_VER )
#include <intrin.h>
#endif
inline size_t query_intel_x86_eflags(const size_t query_bit_mask)
{
#if defined( _MSC_VER )
return __readeflags() & query_bit_mask;
#elif defined( __GNUC__ )
// This code will work only on 64-bit GNU-C machines.
// Tested and does NOT work with Intel C++ 10.1!
size_t eflags;
__asm__ __volatile__(
"pushfq nt"
"pop %%raxnt"
"movq %%rax, %0nt"
:"=r"(eflags)
:
:"%rax"
);
return eflags & query_bit_mask;
#else
#pragma message("No inline assembly will work with this compiler!")
return 0;
#endif
}
int main(int argc, char **argv)
{
int x = 1000000000;
int y = 20000;
int z = x * y;
int f = query_intel_x86_eflags(0x801);
printf("%Xn", f);
}
如果操作数相乘而不溢出,您将从 query_intel_eflags(0x801)
获得返回值 0,即既不设置进位标志,也不设置溢出标志。在提供的 main(( 示例代码中,发生溢出并且两个标志都设置为 1。此检查并不意味着任何进一步的计算,因此应该非常快。
如果你的数据类型大于你要测试的数据类型(假设你执行 32 位加法,你有一个 64 位类型(,那么这将检测是否发生了溢出。我的例子是 8 位添加。但它可以扩大规模。
uint8_t x, y; /* Give these values */
const uint16_t data16 = x + y;
const bool carry = (data16 > 0xFF);
const bool overflow = ((~(x ^ y)) & (x ^ data16) & 0x80);
它基于本页中解释的概念:https://www.cs.umd.edu/~meesh/cmsc311/clin-cmsc311/Lectures/lecture22/overflow.pdf(https://web.archive.org/web/20170121033813/http://www.cs.umd.edu:80/class/spring2003/cmsc311/Notes/Comb/overflow.html 回程机(
对于 32 位示例,0xFF
变得0xFFFFFFFF
,0x80
变得0x80000000
,最后uint16_t
成为uint64_t
。
注意:这捕获了整数加法/减法溢出,我意识到您的问题涉及乘法。在这种情况下,除法可能是最好的方法。这通常是calloc
实现确保参数在相乘以获得最终大小时不会溢出的一种方式。
最简单的方法是将unsigned long
s转换为unsigned long long
s,进行乘法,然后将结果与0x100000000LL进行比较。
您可能会发现这比您在示例中所做的除法更有效。
哦,它将在 C 和 C++ 中工作(因为您已经用两者标记了问题(。
刚刚看了一下glibc手册。其中提到了整数溢出陷阱(FPE_INTOVF_TRAP
(作为SIGFPE
的一部分。这将是理想的,除了手册中令人讨厌的部分:
FPE_INTOVF_TRAP
整数溢出(在 C 程序中是不可能的,除非您以特定于硬件的方式启用溢出捕获(。
真的有点可惜。
您无法从 C/C++ 访问溢出标志。
某些编译器允许您在代码中插入陷阱指令。在 GCC 上,该选项是 -ftrapv
.
您唯一可以做的可移植且独立于编译器的事情是自己检查溢出。就像你在例子中所做的那样。
但是,-ftrapv
似乎在使用最新的 GCC 对 x86 上没有任何作用。我想这是旧版本的遗留物或特定于其他架构的。我原本希望编译器在每次添加后插入一个 INTO 操作码。不幸的是,它没有这样做。
对于无符号整数,只需检查结果是否小于其中一个参数:
unsigned int r, a, b;
r = a + b;
if (r < a)
{
// Overflow
}
对于有符号整数,您可以检查参数和结果的符号。
不同符号的整数不能溢出,并且仅当结果为不同符号时,同一符号的整数才会溢出:
signed int r, a, b, s;
r = a + b;
s = a>=0;
if (s == (b>=0) && s != (r>=0))
{
// Overflow
}
回答浮点数的相同问题,其中位掩码和移位看起来没有希望。我确定的方法适用于有符号和无符号、整数和浮点数。即使没有更大的数据类型要提升到中间计算,它也可以工作。对于所有这些类型,它不是最有效的,但是因为它确实适用于所有类型,所以值得使用。
符号溢出测试,加减法:
获取表示类型的最大和最小可能值的常量,最大值和最小值。
计算并比较操作数的符号。
一个。如果任一值为零,则加法和减法都不会溢出。跳过剩余的测试。
二.如果迹象相反,则添加不会溢出。跳过剩余的测试。
三.如果符号相同,则减法不能溢出。跳过剩余的测试。
测试最大值的正溢出。
一个。如果两个符号都是正的,并且 MAXVALUE - A <B,则加法将溢出。>
二.如果 B 的符号为负数,而最大值 - A <-B,则减法将溢出。
测试最小值的负溢出。
一个。如果两个符号都是负数,并且 MINVALUE - A> B,则加法将溢出。
二.如果 A 的符号为负数,并且 MINVALUE - A> B,则减法将溢出。
否则,不会溢出。
符号溢出测试、乘法和除法:
获取表示类型的最大和最小可能值的常量,最大值和最小值。
计算操作数的幅度(绝对值(并将其与 1 进行比较。(下面,假设A和B是这些星等,而不是签名的原件。
一个。如果任一值为零,则乘法不能溢出,除法将产生零或无穷大。
二.如果任一值为 1,则乘法和除法不能溢出。
三.如果一个操作数的量级小于 1,而另一个操作数的量级大于 1,则乘法不能溢出。
d.如果幅度都小于 1,则除法不能溢出。
测试最大值的正溢出。
一个。如果两个操作数都大于 1,并且 MAXVALUE/A <B,则乘法将溢出。>
二.如果 B 小于 1,并且最大值 * B <A,则除法将溢出。>
否则,不会溢出。
注意:MINVALUE 的最小溢出由 3 处理,因为我们采用了绝对值。但是,如果ABS(MINVALUE(>MAXVALUE,那么我们将有一些罕见的误报。
下溢的测试类似,但涉及 EPSILON(大于零的最小正数(。
另一个有趣的工具是IOC:用于C/C++的整数溢出检查器。
这是一个修补的 Clang 编译器,它在编译时向代码添加检查。
你得到的输出看起来像这样:
CLANG ARITHMETIC UNDEFINED at <add.c, (9:11)> :
Op: +, Reason : Signed Addition Overflow,
BINARY OPERATION: left (int32): 2147483647 right (int32): 1
CERT 开发了一种使用"as-if"无限范围 (AIR( 整数模型检测和报告有符号整数溢出、无符号整数包装和整数截断的新方法。 CERT发布了一份描述该模型的技术报告,并制作了基于GCC 4.4.0和GCC 4.5.0的工作原型。
AIR 整数模型要么生成与使用无限范围整数获得的值等效的值,要么导致运行时约束冲突。与以前的整数模型不同,AIR 整数不需要精确的陷阱,因此不会破坏或禁止大多数现有优化。
使用汇编语言的解决方案的另一种变体是外部过程。此示例用于在 Linux x64 下使用 g++ 和 fasm 进行无符号整数乘法。
此过程将两个无符号整数参数(32 位(相乘(根据 amd64 规范(第 3.2.3 节参数传递(。
如果类为 INTEGER,则使用序列 %rdi、%rsi、%rdx、%rcx、%r8 和 %r9 的下一个可用寄存器
(EDI 和 ESI 在我的代码中注册((,如果发生溢出,则返回结果或 0。
format ELF64
section '.text' executable
public u_mul
u_mul:
MOV eax, edi
mul esi
jnc u_mul_ret
xor eax, eax
u_mul_ret:
ret
测试:
extern "C" unsigned int u_mul(const unsigned int a, const unsigned int b);
int main() {
printf("%un", u_mul(4000000000,2)); // 0
printf("%un", u_mul(UINT_MAX/2,2)); // OK
return 0;
}
将程序与 asm 对象文件链接。就我而言,在Qt Creator中,将其添加到.pro文件中的LIBS
。
用双精度计算结果。它们有 15 位有效数字。您的要求在 c 上有一个硬上限 10 8 — 它最多可以有8 位数字。因此,如果它在范围内,结果将是精确的,否则它不会溢出。
尝试使用此宏来测试 32 位机器的溢出位(改编了 Angel Sinigersky 的解决方案(
#define overflowflag(isOverflow){
size_t eflags;
asm ("pushfl ;"
"pop %%eax"
: "=a" (eflags));
isOverflow = (eflags >> 11) & 1;}
我将其定义为宏,因为否则溢出位将被覆盖。
接下来是一个带有上述代码隔离的小应用程序:
#include <cstddef>
#include <stdio.h>
#include <iostream>
#include <conio.h>
#if defined( _MSC_VER )
#include <intrin.h>
#include <oskit/x86>
#endif
using namespace std;
#define detectOverflow(isOverflow){
size_t eflags;
asm ("pushfl ;"
"pop %%eax"
: "=a" (eflags));
isOverflow = (eflags >> 11) & 1;}
int main(int argc, char **argv) {
bool endTest = false;
bool isOverflow;
do {
cout << "Enter two intergers" << endl;
int x = 0;
int y = 0;
cin.clear();
cin >> x >> y;
int z = x * y;
detectOverflow(isOverflow)
printf("nThe result is: %d", z);
if (!isOverflow) {
std::cout << ": no overflow occuredn" << std::endl;
} else {
std::cout << ": overflow occuredn" << std::endl;
}
z = x * x * y;
detectOverflow(isOverflow)
printf("nThe result is: %d", z);
if (!isOverflow) {
std::cout << ": no overflow ocurredn" << std::endl;
} else {
std::cout << ": overflow occuredn" << std::endl;
}
cout << "Do you want to stop? (Enter "y" or "Y)" << endl;
char c = 0;
do {
c = getchar();
} while ((c == 'n') && (c != EOF));
if (c == 'y' || c == 'Y') {
endTest = true;
}
do {
c = getchar();
} while ((c != 'n') && (c != EOF));
} while (!endTest);
}
在 C 中捕获整数溢出指出了一个比 CERT 讨论的解决方案更通用的解决方案(就处理类型而言,它更通用(,即使它需要一些 GCC 扩展(我不知道它们有多广泛支持(。
无法从 C/C++ 访问溢出标志。
我不同意这一点。您可以编写一些内联汇编语言并使用 jo
(跳转溢出(指令,假设您在 x86 上捕获溢出。当然,您的代码将不再可移植到其他体系结构。
看看info as
和info gcc
.
>mozilla::CheckedInt<T>
为整数类型T
提供溢出检查的整数数学(使用Clang和GCC上的编译器内部函数(。该代码在MPL 2.0下,依赖于三个(IntegerTypeTraits.h
,Attributes.h
和Compiler.h
(其他仅标头的非标准库标头以及Mozilla特定的断言机制。如果导入代码,您可能希望替换断言机制。
为了扩展 Head Geek 的答案,有一种更快的方法来做addition_is_safe
;
bool addition_is_safe(unsigned int a, unsigned int b)
{
unsigned int L_Mask = std::numeric_limits<unsigned int>::max();
L_Mask >>= 1;
L_Mask = ~L_Mask;
a &= L_Mask;
b &= L_Mask;
return ( a == 0 || b == 0 );
}
这使用机器架构安全,因为 64 位和 32 位无符号整数仍然可以正常工作。基本上,我创建了一个遮罩,它将遮盖除最重要的位之外的所有内容。然后,我屏蔽了两个整数,如果它们中的任何一个没有设置该位,那么加法是安全的。
如果您在某个构造函数中预初始化掩码,这会更快,因为它永远不会更改。
x86 指令集包括一个无符号的乘法指令,该指令将结果存储到两个寄存器。要使用 C 中的该指令,可以在 64 位程序 (GCC( 中编写以下代码:
unsigned long checked_imul(unsigned long a, unsigned long b) {
unsigned __int128 res = (unsigned __int128)a * b;
if ((unsigned long)(res >> 64))
printf("overflow in integer multiply");
return (unsigned long)res;
}
对于 32 位程序,需要使结果为 64 位,参数为 32 位。
另一种方法是使用依赖于编译器的内部函数来检查标志寄存器。有关溢出内部函数的 GCC 文档可以在 6.56 使用溢出检查执行算术的内置函数中找到。
一个干净的方法是覆盖所有运算符(特别是 + 和 *(并在执行操作之前检查溢出。
MSalter的回答是个好主意。
如果需要整数计算(为了精度(,但浮点可用,则可以执行以下操作:
uint64_t foo(uint64_t a, uint64_t b) {
double dc;
dc = pow(a, b);
if (dc < UINT_MAX) {
return (powu64(a, b));
}
else {
// Overflow
}
}
这取决于你用它来做什么。执行无符号长 (DWORD( 加法或乘法,最好的解决方案是使用 ULARGE_INTEGER。
ULARGE_INTEGER是两个 DWORD 的结构。全部价值可以作为"四部分"访问,同时访问高 DWORD作为"HighPart",低DWORD作为"LowPart"访问。
例如:
DWORD
My Addition(DWORD Value_A, DWORD Value_B)
{
ULARGE_INTEGER a, b;
b.LowPart = Value_A; // A 32 bit value(up to 32 bit)
b.HighPart = 0;
a.LowPart = Value_B; // A 32 bit value(up to 32 bit)
a.HighPart = 0;
a.QuadPart += b.QuadPart;
// If a.HighPart
// Then a.HighPart contains the overflow (carry)
return (a.LowPart + a.HighPart)
// Any overflow is stored in a.HighPart (up to 32 bits)
要执行无符号乘法而不会以可移植方式溢出,可以使用以下内容:
... /* begin multiplication */
unsigned multiplicand, multiplier, product, productHalf;
int zeroesMultiplicand, zeroesMultiplier;
zeroesMultiplicand = number_of_leading_zeroes( multiplicand );
zeroesMultiplier = number_of_leading_zeroes( multiplier );
if( zeroesMultiplicand + zeroesMultiplier <= 30 ) goto overflow;
productHalf = multiplicand * ( c >> 1 );
if( (int)productHalf < 0 ) goto overflow;
product = productHalf * 2;
if( multiplier & 1 ){
product += multiplicand;
if( product < multiplicand ) goto overflow;
}
..../* continue code here where "product" is the correct product */
....
overflow: /* put overflow handling code here */
int number_of_leading_zeroes( unsigned value ){
int ctZeroes;
if( value == 0 ) return 32;
ctZeroes = 1;
if( ( value >> 16 ) == 0 ){ ctZeroes += 16; value = value << 16; }
if( ( value >> 24 ) == 0 ){ ctZeroes += 8; value = value << 8; }
if( ( value >> 28 ) == 0 ){ ctZeroes += 4; value = value << 4; }
if( ( value >> 30 ) == 0 ){ ctZeroes += 2; value = value << 2; }
ctZeroes -= x >> 31;
return ctZeroes;
}
#include <stdio.h>
#include <stdlib.h>
#define MAX 100
int mltovf(int a, int b)
{
if (a && b) return abs(a) > MAX/abs(b);
else return 0;
}
main()
{
int a, b;
for (a = 0; a <= MAX; a++)
for (b = 0; b < MAX; b++) {
if (mltovf(a, b) != (a*b > MAX))
printf("Bad calculation: a: %d b: %dn", a, b);
}
}
测试溢出的简单方法是通过检查当前值是否小于以前的值来进行验证。 例如,假设您有一个循环来打印 2 的幂:
long lng;
int n;
for (n = 0; n < 34; ++n)
{
lng = pow (2, n);
printf ("%lin", lng);
}
按照我描述的方式添加溢出检查会导致:
long signed lng, lng_prev = 0;
int n;
for (n = 0; n < 34; ++n)
{
lng = pow (2, n);
if (lng <= lng_prev)
{
printf ("Overflow: %in", n);
/* Do whatever you do in the event of overflow. */
}
printf ("%lin", lng);
lng_prev = lng;
}
它适用于无符号值以及正有符号值和负符号值。
当然,如果你想做类似的事情来减少值而不是增加值,你可以翻转<=
符号以使其>=
,假设下溢的行为与溢出的行为相同。 老实说,这与无需访问 CPU 的溢出标志即可获得的可移植性(这需要内联汇编代码,使您的代码无论如何都无法跨实现移植(。