在我的软件中,我在运行时使用用户的输入值并执行一些数学运算。为简单起见,请考虑以下示例:
int multiply(const int a, const int b)
{
if(a >= INT_MAX || B >= INT_MAX)
return 0;
else
return a*b;
}
我可以检查输入值是否大于限制,但如何检查结果是否超出限制? a = INT_MAX - 1
和b = 2
很有可能。由于输入完全有效,它将执行makes my program meaningless的未定义代码。这意味着在此之后执行的任何代码都是随机的,最终可能导致崩溃。那么在这种情况下如何保护我的程序?
答案 0 :(得分:5)
这实际上归结为在这种情况下你真正想做的事情。
对于long
或long long
(或int64_t
)为64位值且int
为32位值的计算机,您可以这样做(我假设long
在这里是64位):
long x = static_cast<long>(a) * b;
if (x > MAX_INT || x < MIN_INT)
return 0;
else
return static_cast<int>(x);
通过将一个值强制转换为long
,另一个值也必须进行转换。你可以投两个,如果这让你更快乐。在正常的32位乘法之上的开销在现代CPU上是几个时钟周期,并且你不太可能找到更安全的解决方案,这也更快。 [在某些编译器中,您可以向if
添加属性,说它不太可能鼓励分支预测&#34;使其正确“#34;对于返回x
]
显然,对于类型与你可以处理的最大整数一样大的值,这不会起作用(虽然你可以使用浮点,但它可能仍然有点狡猾,因为精度浮动是不够的 - 如果您不需要整个整数范围,可以使用某些&#34;安全边界&#34; tho&#34; [例如,比较小于LONG_INT_MAX / 2
]。 )。这里的惩罚有点糟糕,特别是浮动和整数之间的转换并不令人愉快&#34;。
另一种选择是实际测试相关代码,使用&#34;已知的无效值&#34;,并且只要代码的其余部分是&#34; ok&#34;用它。确保使用相关的编译器设置对此进行测试,因为更改编译器选项将更改行为。请注意,您的代码必须处理&#34;当65536 * 100000
为负数&#34;时我们该怎么做,而您的代码并非如此期望。也许添加如下内容:
int x = a * b;
if (x < 0) return 0;
[但这只有在您不希望出现负面结果的情况下才有效]
您还可以检查生成的汇编代码并了解实际处理器的架构[这里的关键是要了解&#34;溢出是否会陷阱&#34; - 默认情况下它在x86,ARM,68K,29K中胜出。我认为MIPS可以选择&#34;陷阱溢出&#34;],并确定它是否可能导致问题[1],并添加类似
的内容#if (defined(__X86__) || defined(__ARM__))
#error This code needs inspecting for correct behaviour
#endif
return a * b;
然而,这种方法的一个问题是,即使代码或编译器版本中的最轻微变化都可能改变结果,因此将此与上述测试方法相结合非常重要(并确保测试实际的生产代码,而不是一些破解迷你的例子。
[1]&#34;未定义的行为&#34;未定义以允许C到&#34;工作&#34;在具有陷阱溢出整数数学的处理器上,以及a * b
当它以有符号值溢出时这一事实当然很难确定,除非你有一个定义的数学系统(两个补码,一个补码,不同的符号位) - 所以要避免&#34;定义&#34;在这些情况下的确切行为,C标准说&#34;它未定义&#34;。这并不意味着它肯定会变坏。
答案 1 :(得分:3)
特别是a
与b
的乘法,检测它是否会溢出的数学上正确的方法是计算两个值的log 2。如果它们的总和高于结果的最高可表示值的log 2,则存在溢出。
log₂(a) + log₂(b) < log₂(UINT_MAX)
难点在于快速计算整数的log 2。为此,有几个可以使用的小工具,比如计数位,计数前导零(某些处理器甚至有指令)。该站点有几个实现 https://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
最简单的实现可能是:
unsigned int log2(unsigned int v)
{
unsigned int r = 0;
while (v >>= 1)
r++;
return r;
}
在你的程序中,你只需要检查
if(log2(a) + log2(b) < MYLOG2UINTMAX)
return a*b;
else
printf("Overflow");
签名的案件类似,但必须专门处理否定案件。
编辑:我的解决方案尚未完成,并且出现错误,导致测试比必要的更严重。如果log 2函数返回浮点值,则该等式实际上起作用。在实现中,我将value限制为无符号整数。这意味着完全有效的乘法会被拒绝。为什么?因为log2(UINT_MAX)
被截断了
log 2(UINT_MAX)= log 2(4294967295)≈31.9999999997截断为31。
我们在那里更改实现以替换常量以与
进行比较#define MYLOG2UINTMAX (CHAR_BIT*sizeof (unsigned int))
答案 2 :(得分:2)
你可以试试这个:
if ( b > ULONG_MAX / a ) // Need to check a != 0 before this division
return 0; //a*b invoke UB
else
return a*b;