我在理解这些关于检测整数溢出的注释时遇到了一些困难

时间:2017-07-29 18:42:42

标签: c++ c integer-overflow

您可以在文章Understanding Integer Overflow in C/C++I. Introduction部分找到引用的文字(重点是我的):

  

使用a检测整数溢出相对简单   修改编译器以插入运行时检查。但是,可靠   检测溢出错误是非常困难的,因为   溢出行为并不总是错误。 C和C的低级性质   C ++意味着对象的位和字节级操作   平凡;数学和位级操作之间的界限   通常很模糊。使用无符号整数的环绕行为   是合法的,定义明确的,并且有故意的代码习语   用它。 另一方面,C和C ++具有未定义的语义   签名溢出并转移超过位宽:完美的操作   在Java等其他语言中定义明确。 C / C ++程序员是   并不总是知道有符号和无符号类型的不同规则   在C中,可以天真地在有意的环绕中使用签名类型   操作。 1 如果这种用途很少,基于编译器的溢出检测   是一种执行整数错误检测的合理方法。如果是   然而,并不罕见,这种方法是不切实际的   需要复杂的技术来区分故意的   使用无意识的。

我不明白为什么基于编译器的检测对于检测签名类型的环绕操作是不切实际的,如果这种使用并不罕见?另外,为什么我们需要区分有意和无意的使用?两者都是标准的未定义行为。

3 个答案:

答案 0 :(得分:1)

在运行时检测有符号整数溢出是没有问题的。像Swift这样的新语言可以自动可靠地完成。

问题是:虽然整数溢出在C和C ++中是未定义的行为,但是有大量的代码会发生整数溢出,并且因为编译器默默地忽略整数溢出,所以一切正常。

如果您开始检测整数溢出,则此类用法将破坏应用程序。当然,当开发人员运行应用程序或测试人员运行它时,这些溢出不会发生,但只有当程序发送给客户时,如果他们的应用程序崩溃最不合适且成本最高,他们将非常非常生气时间,只是因为你决定不允许一些工作正常的未定义行为。

答案 1 :(得分:0)

对于编译器在编译时检测溢出,除了最简单的情况之外,将要求编译器考虑可能影响变量的每个可能输入并计算可能导致的每个可能值。

显然,这是不现实的。

利用溢出的一个例子就是将副作用用于其他方面。这是环形缓冲区的一个人为的例子:

 int main()
 {
   uint8 index = 8;
   char keys[256];

   init_keys(keys); // Put single chars in the array
   while(1) {
     int letter;
     letter = getc();
     letter ^= keys[index];
     index ++;
     printf("Encoded: %c\n", letter);
   }
 }

在这个例子中,我们创建一个8位整数,必须在255 + 1处溢出。我们利用这个溢出直接用这个值实现一个环形缓冲区,而不是使用模数,这更典型。

答案 2 :(得分:0)

有5种合理的方法可以处理溢出,无论是签名还是未签名:

  1. 陷。通常需要额外说明。
  2. 饱和。本机很少提供,通常需要额外的说明。
  3. 环绕式。始终可用于所有但不是2s补码的签名类型。用于无符号类型。
  4. 未定义的行为。始终可用,并允许编译器进行优化假设。用于签名类型。
  5. 任意结果。始终以原生方式提供。只有当环绕本身不可用时才有意思。这比UB弱,这是它最大的优势和劣势。
  6. UB适用于优化,陷阱用于错误检测,环绕和饱和有时是需要的行为。
    任意结果填补了环绕价格昂贵的缺口,但完整的UB没有保证。

    现在,有时编译器可以证明操作不会溢出,因此它不需要处理这种情况。通常用于循环计数器等,因此额外的工作并不像它看起来那么多。但是,即使有完整的源代码,跟踪数据可能具有哪些值也是不完美的,并且在允许的情况下单独编译和语义插入等内联的障碍可能使其无法实现。