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

Des*_*tor 60 c++ unsigned-integer

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

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

Ami*_*ory 49

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

for(size_t i = foo.size(); i >= 0; --i)
    ...
Run Code Online (Sandbox Code Playgroud)

请注意,根据定义,i >= 0始终为真.(是什么原因导致这首先是,如果i签订,编译器会警告与一个可能溢出size_tsize()).

还有其他原因提到危险 - 这里使用的是无符号类型!在我看来,其中最强大的是签名和未签名之间的隐式类型转换.

  • @AndyT获得更好的编译器.http://coliru.stacked-crooked.com/a/c79fc9148dfb5f3f (9认同)
  • 是时候使用`operator - >`([go down to](http://stackoverflow.com/q/1642028)):`for(size_t i = sz; i - > 0;)...`iterates从`sz-1`到'0` (6认同)
  • 这没有证明无符号整数的问题.这表明代码本身存在问题.提倡为工作避免使用适当的工具,因为它们使用效果不佳并不能对任何人产生任何好处.只是不要使用它们. (4认同)

Bau*_*gen 36

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

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

看起来不错,不是吗?它甚至可以用非常高的警告级别进行干净编译!(实时)所以你把它放在你的代码中,所有的测试运行顺利,你忘了它.

现在,稍后,有人来了一个空的通行证vector到你的功能.现在有一个带符号的整数,你希望你会注意到符号比较编译器警告,引入了适当的强制转换,而不是首先发布了错误代码.

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

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

这也是一个安全问题,特别是它是一个缓冲区溢出.可能利用这种方法的一种方法是,如果do_something能做一些攻击者可以观察到的事情.他或许可以找到输入的内容do_something,而攻击者无法访问的数据会从您的内存中泄露出来.这将是一个类似于Heartbleed bug的场景.(感谢棘轮怪人在评论中指出这一点.)

  • 对于这个被指控的反例,我总是感到不安.确实,只要通过近视查看代码,您就会认为签名整数在这里更好.但是,这忽略了更大的算法问题:该算法显然希望特别处理该范围的最后一个元素.因此,这个算法应该有某种先决条件或分支,实际上确保范围*有*最后一个元素!有了这样的分支,无符号整数就可以正常工作. (23认同)
  • 再说一次,这是糟糕的代码.变量类型也不错.不会这样.整数不容易出错.*编程*容易出错. (9认同)
  • @SiyuanRen我用减法*因为它错了*.这个问题和答案的重点是突出*潜在的*错误.没有人试图争辩说这些错误不可修复或可以避免.我只是认为这样的事情可能会发生,而且会很糟糕.所以是的,您可以使用您的代码,然后使用正确的代码.关键是一个*可以*(很容易)弄错(就像我故意在我的回答中做的那样). (4认同)
  • 为什么每个人都必须在这里使用减法?为什么不`for(std :: size_t i = 0; i + 1 <vec.size(); ++ i)`? (3认同)
  • @fyngyrz 当然,编程很容易出错。我们寻找防止错误的工具(在这种情况下是有符号类型)*因为*编程容易出错,并且*因为*人们会犯错误。 (2认同)
  • @fyngyrz:恕我直言,`unsigned int`是一个非常好的变量类型,如果要执行模块化算术,则在语义上*不适当* [不是“坏”]类型,如果一个代表数量。 (2认同)
  • @fyngyrz 如果有足够多的程序员不会犯错误并且在第一次尝试时就做对了,那可能是有道理的。不幸的是,我还没有遇到这样的人。在我们拥有足够多的完美程序员之前,我们需要工具来防止和检测所有*将*发生的错误。 (2认同)

Mik*_*our 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";
    }
}
Run Code Online (Sandbox Code Playgroud)

促销规则意味着i转换unsigned为比较,给出大的正数和惊人的结果.

  • 没有downvote,但只是一个猜测:如果您的编译器允许您这样做,那么您正在编译太少的警告标志 (9认同)
  • @example - 你的编译器**必须**让你这样做; 代码形式良好,其含义明确.当然,警告可能有助于发现逻辑错误,但这不是编译器的主要责任. (8认同)
  • 通过在`unsigned n = 2;之间进行比较,可以使结果更有趣; int i = -1,j = 1;`然后将观察到`n <i`,`i <j`和`j <n`都是真的. (7认同)
  • 该文本应为"C++ IS BROKEN".@PeteBecker说"它的含义很明确"; 这是正确的,但这个定义在数学上是荒谬的.如果要生成整数结果,则将"i"转换为"unsigned"更难以避免,但是为了进行比较,正确定义语言是微不足道的.甚至COBOL _had_**也有**"On size error",但是C(++)只是给你足够的绳索来吊死自己!在VMS上,DEC C(不知道关于++)警告有关签名/未签名的比较/赋值,也是正确的(鉴于语言破碎), (5认同)
  • downvote的任何理由?如果错了,我想纠正答案. (3认同)
  • @PeteBecker您真的认为混合比较的代码“又大又慢”?在很少的应用程序中,额外的1(否定分支)或2(测试,分支)指令很重要,人们可以通过正确键入内容来进行优化。基本设计原则是“快速和错误”吗? (2认同)
  • @PJTraill-您确实不是通过将这个线程拖入棘轮孔来进行OP服务。 (2认同)
  • @PeteBecker“如果想要又大又慢,请使用C#编程。” -这是一种不专业的评论,不应该像您这样的成熟程序员,尤其是您对C ++所做的贡献。如果有人要顺着老鼠洞走,那就是你。 (2认同)

Mar*_*o13 11

虽然它可能只被视为现有答案的变体:参考Scott Meyers 1995年9月"接口中的签名和无符号类型",C++报告,避免接口中的无符号类型尤为重要.

问题是,无法检测到界面客户端可能产生的某些错误(如果他们能够制作它们,他们就会制作它们).

给出的例子是:

template <class T>
  class Array {
  public:
      Array(unsigned int size);
  ...
Run Code Online (Sandbox Code Playgroud)

以及此类的可能实例化

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()
Run Code Online (Sandbox Code Playgroud)

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


gna*_*729 5

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");
Run Code Online (Sandbox Code Playgroud)

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

如果你使用一个好的编译器,你会打开编译器产生的所有警告,当你做了可能是错误的危险事情时,它会告诉你。

  • 一个更棘手的问题是,给定的“uint32_t x,y,z;”表达式(如“xy &gt; z”)在 32 位和 64 位系统上将具有非常不同的含义。 (2认同)