如何在不执行的情况下捕获未定义的行为?

时间:2014-06-21 06:59:27

标签: c++ c

在我的软件中,我在运行时使用用户的输入值并执行一些数学运算。为简单起见,请考虑以下示例:

int multiply(const int a, const int b)
{
    if(a >= INT_MAX || B >= INT_MAX)
        return 0;
    else
        return a*b;
}

我可以检查输入值是否大于限制,但如何检查结果是否超出限制? a = INT_MAX - 1b = 2很有可能。由于输入完全有效,它将执行makes my program meaningless的未定义代码。这意味着在此之后执行的任何代码都是随机的,最终可能导致崩溃。那么在这种情况下如何保护我的程序?

3 个答案:

答案 0 :(得分:5)

这实际上归结为在这种情况下你真正想做的事情。

对于longlong 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)

特别是ab的乘法,检测它是否会溢出的数学上正确的方法是计算两个值的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;