我在C ++编写一个程序来找到 B = C,其中A,B和C一起使用所有的数字0-9只出现一次的所有解决方案。 该程序循环了a和b的值,并且每次在a , b和a b上运行一个数字计数例程,以检查是否满足数字条件。
然而,当A 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 ++访问它。
请注意, 在C和C ++中 ,有符号的 int
溢出是未定义的行为 ,因此您必须在没有实际引起它的情况下对其进行检测。 有关添加之前的有符号的int溢出,请参阅在C / C ++中检测有符号的溢出 。
#1楼
对于浮点数,我需要回答相同的问题,在这种情况下,位掩码和移位看起来不太理想。 我确定的方法适用于有符号和无符号,整数和浮点数。 即使没有更大的数据类型可以推广到中间计算,它也可以工作。 对于所有这些类型,它并不是最有效的,但是由于它确实对所有这些类型都有效,因此值得使用。
签名溢出测试,加法和减法:
获取常量,该常量表示类型MAXVALUE和MINVALUE的最大和最小值。
计算并比较操作数的符号。
一种。 如果任一值为零,则加法或减法都不会溢出。 跳过其余测试。
b。 如果符号相反,则添加不会溢出。 跳过其余测试。
C。 如果符号相同,则减法不会溢出。 跳过其余测试。
测试MAXVALUE的正溢出。
一种。 如果两个符号均为正且MAXVALUE-A <B,则加法运算将溢出。
b。 如果B的符号为负且MAXVALUE-A <-B,则减法将溢出。
测试MINVALUE的负溢出。
一种。 如果两个符号均为负且MINVALUE-A> B,则加法运算将溢出。
b。 如果A的符号为负且MINVALUE-A> B,则减法将溢出。
否则,不会溢出。
签名溢出测试,乘法和除法:
获取常量,该常量表示类型MAXVALUE和MINVALUE的最大和最小值。
计算并比较操作数的大小(绝对值)为一。 (下面,假设A和B是这些幅度,而不是签名的正本。)
一种。 如果任一值为零,则乘法不会溢出,除法将得出零或无穷大。
b。 如果任一值为1,则乘法和除法不会溢出。
C。 如果一个操作数的大小小于一个,而另一个大于一个,则乘法不会溢出。
d。 如果幅度均小于1,则除法运算不会溢出。
测试MAXVALUE的正溢出。
一种。 如果两个操作数均大于1且MAXVALUE / A <B,则乘法将溢出。
b。 如果B小于1并且MAXVALUE * B <A,则除法将溢出。
否则,不会溢出。
注意:MINVALUE的最小溢出由3处理,因为我们采用了绝对值。 但是,如果ABS(MINVALUE)> MAXVALUE,那么我们将有一些罕见的误报。
下溢测试相似,但涉及EPSILON(最小的正数大于零)。
#2楼
另一个有趣的工具是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
#3楼
这是该问题的“非便携式”解决方案。 Intel x86和x64 CPU具有所谓的EFLAGS寄存器 ,该寄存器在每次整数算术运算后由处理器填充。 我将在此处跳过详细描述。 相关标志是“ Overflow”标志(掩码0x800)和“ Carry”标志(掩码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 \n\t"
"pop %%rax\n\t"
"movq %%rax, %0\n\t"
:"=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("%X\n", f);
}
如果将操作数相乘而没有溢出,则您将从query_intel_eflags(0x801)
获得的返回值为0,即未设置进位或溢出标志。 在所提供的main()示例代码中,发生溢出,并且两个标志都设置为1。此检查并不意味着进行任何进一步的计算,因此它应该非常快。
#4楼
Clang现在支持对有符号和无符号整数进行动态溢出检查。 请参阅-fsanitize = integer开关。 目前,它是唯一具有完全支持的动态溢出检查(用于调试)的C ++编译器。
#5楼
为了扩展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位无符号整数仍然可以正常工作。 基本上,我创建了一个遮罩,该遮罩将掩盖除最高有效位以外的所有部分。 然后,我将两个整数都屏蔽了,如果其中两个都没有设置该位,那么加法是安全的。
如果在某些构造函数中预先初始化了掩码,这将更快,因为它永远不会改变。
#6楼
CERT开发了一种新的方法,该方法使用“假设”无限范围(AIR)整数模型来检测和报告带符号整数溢出,无符号整数环绕和整数截断。 CERT发布了描述该模型的技术报告 ,并基于GCC 4.4.0和GCC 4.5.0制作了一个工作原型。
AIR整数模型产生的值等于使用无限范围整数获得的值,或者导致违反运行时约束。 与以前的整数模型不同,AIR整数不需要精确的陷阱,因此不会破坏或抑制大多数现有的优化。
#7楼
我看到您使用的是无符号整数。 按照定义, 在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 (a > INT_MAX / x) /* `a * x` would overflow */;
if ((a < INT_MIN / x)) /* `a * x` would underflow */;
对于除法(特殊情况INT_MIN
和-1
除外),没有可能超过INT_MIN
或INT_MAX
。
#8楼
我看到很多人回答了有关溢出的问题,但是我想解决他最初的问题。 他说,问题在于找到一个b = c使得所有数字都无需重复就可以使用。 好的,这不是他在这篇文章中所要求的,但是我仍然认为有必要研究问题的上限并得出结论,他永远不需要计算或检测溢出(请注意:我不是精通在数学上,所以我逐步进行了此操作,但是最终结果非常简单,可能会有一个简单的公式。
要点是,问题对于a,b或c所需的上限是98.765.432。 无论如何,首先将问题分解为琐碎的部分和非琐碎的部分:
- x 0 == 1(9、8、7、6、5、4、3、2的所有排列都是解)
- x 1 == x(无解)
- 0 b == 0(无解)
- 1 b == 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)
#9楼
尝试使用此宏来测试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 occured\n" << std::endl;
} else {
std::cout << ": overflow occured\n" << std::endl;
}
z = x * x * y;
detectOverflow(isOverflow)
printf("\nThe result is: %d", z);
if (!isOverflow) {
std::cout << ": no overflow ocurred\n" << std::endl;
} else {
std::cout << ": overflow occured\n" << 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);
}
#10楼
这取决于您使用它的目的。 执行无符号长(DWORD)加法或乘法,最好的解决方案是使用ULARGE_INTEGER。
ULARGE_INTEGER是两个DWORD的结构。 完整值可以作为“ QuadPart”来访问,而高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)
#11楼
内联汇编使您可以直接检查溢出位。 如果要使用C ++,则应该学习汇编语言。
#12楼
最简单的方法是将unsigned long
s转换为unsigned long long
,进行乘法运算,然后将结果与0x100000000LL进行比较。
您可能会发现,这比示例中的分割效率更高。
哦,它可以同时在C和C ++中使用(因为您已经用这个标记了问题)。
刚刚看过glibc手册 。 作为SIGFPE
一部分,提到了整数溢出陷阱( FPE_INTOVF_TRAP
)。 除了手册中令人讨厌的内容,这将是理想的:
FPE_INTOVF_TRAP
整数溢出(除非您以硬件特定的方式启用溢出陷阱,否则在C程序中是不可能的)。
真的有点可耻。
#13楼
您不能从C / C ++访问溢出标志。
一些编译器允许您将陷阱指令插入代码。 在GCC上,选项是-ftrapv
。
您可以做的唯一可移植且独立于编译器的操作是自行检查溢出。 就像您在示例中所做的一样。
但是,使用最新的GCC, -ftrapv
在x86上似乎无能为力。 我想这是旧版本或其他一些体系结构遗留下来的。 我曾希望编译器在每次添加后都插入一个INTO操作码。 不幸的是,它没有这样做。
#14楼
一些编译器提供对CPU中整数溢出标志的访问,然后可以对其进行测试,但这不是标准的。
您也可以在执行乘法之前测试溢出的可能性:
if ( b > ULONG_MAX / a ) // a * b would overflow
#15楼
一种干净的方法是覆盖所有运算符(尤其是+和*),并在执行操作之前检查是否有溢出。
#16楼
您不能从C / C ++访问溢出标志。
我不同意这一点。 您可以编写一些内联汇编语言,并使用jo
(跳转溢出)指令,假设您在x86上以捕获溢出。 当然,您的代码将不再可移植到其他体系结构。
查看info as
和info gcc
。
#17楼
有一种方法来确定操作是否可能溢出,使用最显著一个位的操作对象的位置和一点点基本的二进制数学知识。
另外,任何两个操作数的结果(最多)将比最大操作数的最高一位多1位。 例如:
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
函数中的循环快于按照建议的方式简单地检查结果,但是这样做可能会highestOneBitPosition
(特别是如果您事先知道操作数中有多少位)。
#18楼
如果您的数据类型大于要测试的数据类型(例如,您进行了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);
它基于本页上解释的概念: http : //www.cs.umd.edu/class/spring2003/cmsc311/Notes/Comb/overflow.html
对于32位示例, 0xFF
变为0xFFFFFFFF
, 0x80
变为0x80000000
,最后uint16_t
变为uint64_t
。
注意 :这会捕获整数加减运算,我意识到您的问题涉及乘法。 在这种情况下,划分可能是最好的方法。 这通常是calloc
实现确保将参数相乘以获得最终大小时不会溢出的一种方式。
#19楼
计算结果加倍。 他们有15位有效数字。 您的要求的c的上限是10 8-最多可以有8位数字。 因此,如果结果在范围内,则结果将是精确的,否则它将不会溢出。
#20楼
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
}
}
#21楼
Clang 3.4+和GCC 5+提供了经过检查的算术内置函数 。 它们为这个问题提供了非常快速的解决方案,特别是与位测试安全检查相比。
对于OP问题中的示例,它将像这样工作:
unsigned long b, c, c_test;
if (__builtin_umull_overflow(b, c, &c_test))
{
// Returned non-zero: there has been an overflow
}
else
{
// Return zero: there hasn't been an overflow
}
如果发生溢出,Clang文档未指定c_test
是否包含溢出结果,但GCC文档指出确实如此。 鉴于这两个都是__builtin
兼容的,可以安全地假设这也是Clang的工作方式。
对于int大小,long大小和long long大小,每个有符号和无符号变体都可以溢出(加,减,乘)的算术运算都有一个__builtin
。 名称的语法为__builtin_[us](operation)(l?l?)_overflow
:
-
u
为未签名或s
为签名 ; - 操作是
add
,sub
或mul
; - 没有
l
后缀表示操作数是int
; 一l
意味着long
; 两个l
表示long long
。
因此,对于选中的带符号长整数加法运算,它将是__builtin_saddl_overflow
。 完整列表可以在Clang文档页面上找到。
GCC 5+和锵3.8+还提供通用建宏,如果没有指定值的工种: __builtin_add_overflow
, __builtin_sub_overflow
和__builtin_mul_overflow
。 这些也适用于小于int
类型。
内建组件降低到最适合该平台的组件。 在x86上,它们检查进位,溢出和符号标志。
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
是输入的进位/借位标志,返回值是输出的进位/借位。 它似乎没有等效的有符号运算或乘法。
否则,适用于Windows的Clang现在可以投入生产了(对于Chrome来说已经足够好了),因此也可以选择。
#22楼
使用汇编语言的解决方案的另一个变体是外部过程。 此示例在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("%u\n", u_mul(4000000000,2)); // 0
printf("%u\n", u_mul(UINT_MAX/2,2)); // OK
return 0;
}
将程序与asm目标文件链接。 就我而言,在Qt Creator中 ,将其添加到.pro文件中的LIBS
中。
#23楼
#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: %d\n", a, b);
}
}
#24楼
用C捕获整数溢出指出了比CERT讨论的解决方案更通用的解决方案(就处理类型而言更通用),即使它需要一些GCC扩展(我不知道它们的支持程度如何)。
#25楼
要执行无符号乘法而不会以可移植的方式溢出,可以使用以下方法:
... /* 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;
}
#26楼
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位。
一种替代方法是使用依赖于编译器的内在函数来检查标志寄存器。 可以从6.56内置函数通过溢出检查执行算术运算中找到有关溢出固有函数的GCC文档。
#27楼
mozilla::CheckedInt<T>
为整数类型T
提供经过溢出检查的整数数学运算(使用clang和gcc上的编译器固有函数)。 该代码在MPL 2.0下并且依赖于三个(仅IntegerTypeTraits.h
, Attributes.h
和Compiler.h
)其他仅标头的非标准库标头以及Mozilla特定的断言机制 。 如果导入代码,则可能要替换断言机制。
#28楼
对于无符号整数,只需检查结果是否小于参数之一:
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
}
#29楼
这是一种检测至少溢出的溢出的快速方法,这可能会导致乘法,除法和幂运算。
这样做的原因在于,正是因为处理器会将值回绕为零,并且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 bool overflow = (x >> 16U) * (y >> 16U);
uint32_t value = overflow ? UINT32_MAX : x * y;
这将导致UINT32_MAX溢出或相乘的结果。 在这种情况下,完全不允许使用有符号整数进行乘法运算。
#30楼
警告:使用-O2
编译时,GCC可以优化掉溢出检查。 -Wall
选项在某些情况下会向您发出警告
if (a + b < a) { /* Deal with overflow */ }
但在此示例中不是:
b = abs(a);
if (b < 0) { /* Deal with overflow */ }
唯一安全的方法是,如CERT文件所述,在溢出发生之前进行检查,这对于系统地使用将非常繁琐。
使用-fwrapv
编译可以解决该问题,但是会禁用某些优化。
我们迫切需要更好的解决方案。 我认为在进行不依赖溢出的优化时,编译器默认应发出警告。 目前的情况允许编译器优化溢出检查,这在我看来是不可接受的。