我看了这个视频。 Bjarne Stroustrup说 无符号整数容易出现错误并导致错误,因此只有在确实需要时才应使用它们。我还在Stack Overflow的一个问题中读到(但我不记得是哪个问题),使用无符号整数可能导致安全漏洞。
他们如何导致安全漏洞?可以通过给出合适的例子来清楚地解释吗?
我看了这个视频。 Bjarne Stroustrup说 无符号整数容易出现错误并导致错误,因此只有在确实需要时才应使用它们。我还在Stack Overflow的一个问题中读到(但我不记得是哪个问题),使用无符号整数可能导致安全漏洞。
他们如何导致安全漏洞?可以通过给出合适的例子来清楚地解释吗?
可能的一个方面是,无符号整数在循环中可能会导致一些不太容易发现的问题,因为下溢会导致出现大数字。我甚至用无符号整数都数不清楚有多少次犯了这种错误的变体。
for(size_t i = foo.size(); i >= 0; --i)
...
请注意,根据定义,i >= 0
总是为真。 (首先导致这种情况的原因是如果 i
是带符号的,编译器将警告可能会发生 size()
的 size_t
溢出。)
还有其他原因在危险 - 在此使用无符号类型!中提到,其中我认为最强大的是有符号和无符号之间的隐式类型转换。
一个很大的因素是它使循环逻辑更加困难:想象一下,你想要迭代一个数组中除最后一个元素以外的所有元素(在现实世界中确实会出现这种情况)。于是你编写了你的函数:
void fun (const std::vector<int> &vec) {
for (std::size_t i = 0; i < vec.size() - 1; ++i)
do_something(vec[i]);
}
看起来不错,是吗?甚至可以在非常高的警告级别下进行干净的编译!(实时演示) 所以你把这个代码放到你的项目中,所有测试都顺利运行,然后你忘记了它。
现在,稍后,有人向你的函数传递一个空的vector
。 如果是带符号的整数,您希望会注意到符号比较编译器警告,引入适当的转换,并首先不发布有缺陷的代码。
但是对于无符号整数的实现,您会发现已经出现了环绕,循环条件变成了i < SIZE_T_MAX
。灾难、未定义行为和最有可能的崩溃!
我想知道它们是如何导致安全漏洞的?
这也是一个安全问题,特别是它是缓冲区溢出。一种可能利用它的方法是,如果do_something
会执行可以被攻击者观察到的操作。他们可能能够找到输入进入do_something
的数据,并且这样从内存泄漏出攻击者不应该访问的数据。这将类似于Heartbleed漏洞的情况。(感谢ratchet freak在评论中指出了这一点。)
for (std::size_t i = 0; i + 1 < vec.size(); ++i)
呢? - Siyuan Ren我不会为了回答一个问题而观看视频,但如果混合使用有符号和无符号值,可能会出现令人困惑的转换问题。例如:
#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
进行比较,产生一个大的正数和一个令人惊讶的结果。
unsigned n = 2; int i = -1, j = 1;
进行比较,可以使结果更加有趣。可以发现n < i
、i < j
和j < n
都是成立的。 - supercati
转换为无符号类型,但是对于比较而言,正确地定义语言是微不足道的。即使COBOL带有“On size error”的功能,但C(++)却给了你足够的绳子来让你自杀!在VMS上,DEC C(我不知道++)会警告有关有符号/无符号比较/赋值的问题,这是完全正确的(考虑到这个破碎的语言)。 - PJTraill
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
和非常大的数组分配。请注意保留HTML标记。Array<double>(*ptrToSize)
。 - josefxassert(ptr != nullptr)
可能就足够了。像 assert(size < theSizeThatIsLikelyToBeAllocated)
这样的东西是不起作用的。当然,人们仍然可以使用带符号类型误用API。只是更难,而且最有可能的错误(由于隐式转换等原因引起的错误)可以被覆盖。 - Marco13无符号整数的一个大问题在于,如果你从一个无符号整数0中减去1,结果不是负数,结果也不小于原先这个数字,而结果是最大可能的无符号整数值。
unsigned int x = 0;
unsigned int y = x - 1;
if (y > x) printf ("What a surprise! \n");
这就是unsigned int容易出错的原因。当然,unsigned int按照设计的方式工作得非常准确。如果你知道自己在做什么并且没有犯错误,那么它绝对是安全的。但是大多数人都会犯错。
如果你使用一个好的编译器,打开编译器产生的所有警告,它会告诉你哪些操作是危险的并有可能是错误的。
uint32_t x,y,z;
,表达式 x-y > z
在 32 位和 64 位系统上的含义将会非常不同。 - supercatint
为64位的系统上。我认为标准会受益于定义非提升类型,其行为在接受使用它们的所有编译器上都是一致的。使用wrap32_t
的操作应该在可能的情况下产生该类型的结果,或者拒绝编译(例如因为编译器不支持所需的语义,或者因为代码正在尝试将wrap16_t
和wrap32_t
相加——这种操作不可能产生同时满足两个约束条件的结果)。 - supercat unum32_t
将始终行为像一个数字,而不管 int
的大小(如果int为32位或更小,则可能需要将减法或一元负操作的右操作提升到下一个较大的有符号类型),而 wrap32_t
将始终作为代数环的成员行为(即使int大于32位,也会阻止升级)。 然而,在没有这样的类型的情况下,编写既可移植又干净的代码通常是不可能的,因为可移植代码通常需要在各个地方进行类型强制转换。int
的大小吗? - Martin Baint
更小的整数类型真是个大麻烦,无符号整数尤其如此。 - Steve Jessopunsigned
转换为int
或者反过来,二进制表示法是完全相同的。因此,在将一个类型转换为另一个类型时,CPU不会有任何额外的开销。 - example