我在看this video。 Bjarne Stroustrup表示无符号整数容易出错并导致错误。所以,你应该只在你真正需要的时候使用它们。我还读过有关Stack Overflow的问题之一(但我不记得哪一个)使用 unsigned ints 会导致安全漏洞。
它们如何导致安全漏洞?有人可以通过给出一个合适的例子来清楚地解释它
答案 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)
无符号整数类型的问题在于,根据它们的大小,它们可能代表两种不同的东西之一:
int
的无符号类型(例如uint8
)在0..2ⁿ-1范围内保存数字,并使用它们进行计算将根据规则运行整数运算,只要它们不超过int
类型的范围。根据现行规则,如果这样的计算超出int
的范围,编译器就可以用代码做任何喜欢的事情,甚至可以否定时间和因果关系的规律(一些编译器会这样做)正是这样!),即使将计算结果分配回小于int
的无符号类型。 unsigned int
和更大的保持成员的抽象包装代数环的整数全等mod2ⁿ;这实际上意味着如果计算超出0..2ⁿ-1的范围,系统将增加或减去任何需要2倍的倍数才能使值回到范围内。因此,给定uint32_t x=1, y=2;
,表达式x-y
可能具有两种含义之一,具体取决于int
是否大于32位。
int
大于32位,则表达式将从数字1中减去数字2,得到数字-1。请注意,虽然类型uint32_t
的变量不能保持值-1而不管int
的大小,并且存储-1将导致这样的变量保持0xFFFFFFFF,但除非或直到该值被强制转换为无符号类型,它的行为类似于有符号数量-1。int
为32位或更小,则表达式将生成uint32_t
值,当将其添加到uint32_t
值2时,将生成uint32_t
值1 (即uint32_t
值0xFFFFFFFF)。unum32_t
总是表现为数字,无论int
的大小如何(可能需要将减法或一元减法的右手操作提升到下一个更大的如果int
是32位或更小,则签名类型),而wrap32_t
总是表现为代数环的成员(即使int
大于32位也阻止促销)。然而,在没有这种类型的情况下,编写既便携又干净的代码通常是不可能的,因为可移植代码通常需要在整个地方进行类型强制。
答案 6 :(得分:2)
C和C ++中的数字转换规则是拜占庭式的混乱。使用无符号类型会比使用纯符号类型更大程度地暴露自己。#/ p>
以两个变量之间的比较为例,一个是有符号的,另一个是无符号的。
再举一个例子,考虑将两个相同大小的无符号整数相乘。
答案 7 :(得分:-3)
除了无符号类型的范围/扭曲问题。使用无符号和有符号整数类型的混合会影响处理器的重要性能问题。少于浮动点,但要忽略这一点。此外,编译器可以对值进行范围检查并更改进一步检查的行为。