为什么无符号整数容易出错?

时间:2015-05-22 11:11:03

标签: c++ unsigned-integer

我在看this videoBjarne Stroustrup表示无符号整数容易出错并导致错误。所以,你应该只在你真正需要的时候使用它们。我还读过有关Stack Overflow的问题之一(但我不记得哪一个)使用 unsigned ints 会导致安全漏洞。

它们如何导致安全漏洞?有人可以通过给出一个合适的例子来清楚地解释它

8 个答案:

答案 0 :(得分:49)

一个可能的方面是无符号整数会导致循环中出现一些难以发现的问题,因为下溢会导致大量数据。我无法计算(即使使用无符号整数!)多少次我做了这个bug的变种

for(size_t i = foo.size(); i >= 0; --i)
    ...

请注意,根据定义,i >= 0始终为真。 (首先导致这种情况的原因是,如果i已签名,编译器将警告size_t的{​​{1}}可能出现溢出。

提到Danger – unsigned types used here!还有其他原因,在我看来,其中最强的是签名和无签名之间的隐式类型转换。

答案 1 :(得分:35)

一个重要因素是它使循环逻辑变得更难:想象一下,你想迭代除了数组的最后一个元素(在现实世界中确实发生)。所以你写下你的功能:

void fun (const std::vector<int> &vec) {
    for (std::size_t i = 0; i < vec.size() - 1; ++i)
        do_something(vec[i]);
}

看起来不错,不是吗?它甚至可以用非常高的警告级别进行干净编译! (Live)所以你把它放在你的代码中,所有的测试都能顺利进行,你就会忘掉它。

现在,稍后,有人来了一个空的vector传递给你的函数。现在有一个带符号的整数,你希望你会注意到sign-compare compiler warning,引入了适当的演员,并且没有首先发布了错误的代码。

但是在使用无符号整数的实现中,您将换行并且循环条件变为i < SIZE_T_MAX。灾难,UB,最有可能崩溃!

  

我想知道它们是如何导致安全漏洞的?

这也是一个安全问题,尤其是buffer overflow。可能利用这种方法的一种方法是,do_something会做一些攻击者可以观察到的事情。他可能能够找到输入do_something的内容,而攻击者无法访问的数据会从您的内存中泄露出来。这将是类似于Heartbleed bug的场景。 (感谢棘轮怪人指出他的comment。)

答案 2 :(得分:23)

我不会仅仅为了回答问题而观看视频,但有一个问题是如果混合有符号和无符号值,可能会发生令人困惑的转换。例如:

#include <iostream>

int main() {
    unsigned n = 42;
    int i = -42;
    if (i < n) {
        std::cout << "All is well\n";
    } else {
        std::cout << "ARITHMETIC IS BROKEN!\n";
    }
}

促销规则意味着i会转换为unsigned进行比较,从而产生较大的正数和令人惊讶的结果。

答案 3 :(得分:11)

虽然它可能只被视为现有答案的变体:参考Scott Meyers的"Signed and unsigned types in interfaces," C++ Report, September 1995,避免 接口中的无符号类型尤为重要>

问题在于,无法检测到界面客户端可能产生的某些错误(如果他们可以制作它们,那么制作它们)。

给出的例子是:

template <class T>
  class Array {
  public:
      Array(unsigned int size);
  ...

以及此类的可能实例化

int f(); // f and g are functions that return
int g(); // ints; what they do is unimportant
Array<double> a(f()-g()); // array size is f()-g()

f()g()返回的值之间的差异可能是负面的,原因很多。 Array类的构造函数将接收此差异作为隐式转换为unsigned的值。因此,作为Array类的实现者,人们无法区分-1的错误传递值和非常大的数组分配。

答案 4 :(得分:4)

unsigned int的一个大问题是,如果从unsigned int 0中减去1,结果不是负数,结果不小于你开始的数,但结果是最大的unsigned int值。

unsigned int x = 0;
unsigned int y = x - 1;

if (y > x) printf ("What a surprise! \n");

这就是使unsigned int容易出错的原因。当然unsigned int的工作原理与它的设计完全相同。如果你知道自己在做什么并且没有犯错,那绝对安全。但大多数人都会犯错误。

如果您使用的是良好的编译器,则打开编译器生成的所有警告,它会告诉您何时执行可能存在错误的危险事件。

答案 5 :(得分:2)

无符号整数类型的问题在于,根据它们的大小,它们可能代表两种不同的东西之一:

  1. 小于int的无符号类型(例如uint8)在0..2ⁿ-1范围内保存数字,并使用它们进行计算将根据规则运行整数运算,只要它们不超过int类型的范围。根据现行规则,如果这样的计算超出int的范围,编译器就可以用代码做任何喜欢的事情,甚至可以否定时间和因果关系的规律(一些编译器会这样做)正是这样!),即使将计算结果分配回小于int的无符号类型。
  2. 无符号类型unsigned int和更大的保持成员的抽象包装代数环的整数全等mod2ⁿ;这实际上意味着如果计算超出0..2ⁿ-1的范围,系统将增加或减去任何需要2倍的倍数才能使值回到范围内。
  3. 因此,给定uint32_t x=1, y=2;,表达式x-y可能具有两种含义之一,具体取决于int是否大于32位。

    1. 如果int大于32位,则表达式将从数字1中减去数字2,得到数字-1。请注意,虽然类型uint32_t的变量不能保持值-1而不管int的大小,并且存储-1将导致这样的变量保持0xFFFFFFFF,但除非或直到该值被强制转换为无符号类型,它的行为类似于有符号数量-1。
    2. 如果int为32位或更小,则表达式将生成uint32_t值,当将其添加到uint32_t值2时,将生成uint32_t值1 (即uint32_t值0xFFFFFFFF)。
    3. 恕我直言,如果C和C ++要定义新的无符号类型,这个问题可以彻底解决[例如, unum32_t和uwrap32_t]这样unum32_t总是表现为数字,无论int的大小如何(可能需要将减法或一元减法的右手操作提升到下一个更大的如果int是32位或更小,则签名类型),而wrap32_t总是表现为代数环的成员(即使int大于32位也阻止促销)。然而,在没有这种类型的情况下,编写既便携又干净的代码通常是不可能的,因为可移植代码通常需要在整个地方进行类型强制。

答案 6 :(得分:2)

C和C ++中的数字转换规则是拜占庭式的混乱。使用无符号类型会比使用纯符号类型更大程度地暴露自己。#/ p>

以两个变量之间的比较为例,一个是有符号的,另一个是无符号的。

  • 如果两个操作数小于int,则它们都将转换为int,并且比较将给出数字上正确的结果。
  • 如果无符号操作数小于带符号操作数,则两者都将转换为带符号操作数的类型,并且比较将给出数值正确的结果。
  • 如果无符号操作数的大小大于或等于带符号的操作数,并且大小大于或等于int,则两者都将转换为无符号操作数的类型。如果带符号操作数的值小于零,则会导致数值上不正确的结果。

再举一个例子,考虑将两个相同大小的无符号整数相乘。

  • 如果操作数大小大于或等于int的大小,则乘法将定义环绕语义。
  • 如果操作数大小小于int但大于或等于int大小的一半,则可能存在未定义的行为。
  • 如果操作数大小小于int大小的一半,则乘法将产生数值正确的结果。将此结果分配回原始无符号类型的变量将产生定义的环绕语义。

答案 7 :(得分:-3)

除了无符号类型的范围/扭曲问题。使用无符号和有符号整数类型的混合会影响处理器的重要性能问题。少于浮动点,但要忽略这一点。此外,编译器可以对值进行范围检查并更改进一步检查的行为。