来自Scott Meyers的“Effective C ++ 3rd edition”:
为了强调未定义行为的结果是不可预测的并且可能非常令人不愉快,经验丰富的C ++程序员经常说具有未定义行为的程序可以擦除您的硬盘驱动器。
在什么情况下会发生这种情况?
例如,是否可以访问和写入不属于此C ++程序或线程的阵列范围内的位置损坏的内存?
答案 0 :(得分:25)
可以吗?当然。事实上发生在我身上。
我编写了删除临时目录的代码。这涉及创建recursive delete <temp directory>\*.*
命令。由于存在错误,<temp directory>
字段并未始终填写。我们的文件系统代码很高兴地执行了recursive delete \*.*
命令。
我的同事注意到桌面上的图标突然消失了。拿出两台机器。
答案 1 :(得分:11)
如果考虑到UB不仅可用于用户模式代码,还可用于系统程序员。换句话说,如果您正在使用UB(或其他错误!)编写驱动程序代码,您最终可能会写入一段内存,该内存稍后将被写回“整个磁盘 - 数据结构的根” 。
我确实在我工作过的驱动程序中有一个错误,导致磁盘损坏,因为驱动程序正在使用过时的指针(指针在释放后使用)。如果你是UNLUCKY,那么未使用的内存恰好是文件系统拥有的一个块,所以它会将一些随机垃圾写回磁盘。幸运的是,要确定问题是什么并不太难,我只需要在我的测试系统上重新格式化一次磁盘(在处理驱动程序时,通常使用两台计算机,一台用于构建代码,另一台用于测试代码 - 测试机器通常具有最小的安装集,并且经常相对频繁地重新格式化和重新配置)。
我认为Scott的提及并不一定意味着这种情况,但完全有可能的是,如果你拥有足够多的代码,它几乎可以导致任何事情发生。包括在安全系统中找到漏洞(参见已经成功的所有堆栈漏洞利用)。你可能必须非常不走运,但是人们也会不时地赢得那些大型彩票,所以如果你能够每周一次或每月一次获得几百万次机会,那么一台电脑就可以每秒执行数百万次操作可以实现更不可能的事情......
答案 2 :(得分:7)
来自 C ++ 11标准(实际上来自草案N3337),在1.3条款和定义[intro.defs](强调我的)中
未定义的行为
本国际标准强加的行为无要求
[注意:当本国际标准遗漏任何明确的定义时,可能会出现未定义的行为 行为或程序使用错误的构造或错误数据时。允许的未定义行为 范围从完全忽略情况与不可预测的结果,到翻译期间的行为或 程序以环境特征的文件形式执行(有或没有发行 诊断消息),终止翻译或执行(发布诊断消息)。 许多错误的程序结构不会产生未定义的行为;他们需要被诊断出来。 - 结束记录]
从“无要求”+“不可预测的结果”我们可以得出结论(理论上)任何事情都可能发生。
现在,没有“合理的”编译器故意发出代码来擦除硬盘驱动器,例如,除以0,但是如果你搞砸了它可能会发生如你所说,如果你破坏了内存(编辑:,请参阅MSalters' comment on their own answer)。
这里的要点是:始终小心永远不会调用未定义的行为。 “这就是龙。”
(实际上很难确定你的程序是否有明确的定义。有一些建议。熟悉语言,远离尘土飞扬的角落。如果一段代码看起来很可疑或太复杂,请尝试重写它以使其更简单,更清晰。始终使用最高级别的警告进行编译,并且不要忽略它们。还有像-fcatch-undefined-behavior
这样的编译器标志和lint
之类的工具可以提供帮助。当然是测试,但是有点晚了。)
答案 3 :(得分:6)
理论上,内存违规会导致程序执行错误的代码。如果您非常运气不好,那么可能是删除硬盘驱动器上的内容的代码。我怀疑它不太可能走得那么远,除非你自己处理低级别的磁盘操作。
我认为声明的重点是你需要非常认真地对待未定义的行为,并尽一切可能防范它(即防御性编程)。我已经看到太多糟糕的程序员天真地依赖于一些未定义的行为,假设它将一直工作。在实践中,它是不可预测的,有时结果可能是灾难性的。
答案 4 :(得分:2)
一个简单的例子就是您碰巧损坏了您要写入的块编号或您要删除的文件名。
答案 5 :(得分:2)
是
考虑一个处理外部输入(例如Web应用程序的一个组件)并且具有缓冲区溢出的应用程序,这是一种相当常见的未定义行为。
攻击者注意到这一点并故意制作删除所有数据的输入。 (大多数攻击者实际上并不这样做:他们想要做的是检索您的数据,或者在您的网站上植入内容。但偶尔有些人确实想要删除您的文件。)
损坏的最大程度取决于攻击者能够绕过的安全层。如果未安全地配置服务器,或者攻击者能够利用其他漏洞,则攻击者可能能够获得计算机的管理员权限或将其用作中继来攻击其他计算机。所有这些都来自单个缓冲区溢出。
要避免这种情况的教训是,未定义的行为不仅仅是可能发生的事情。不仅会发生你不会想到的事情(有些编译器非常擅长拾取奇怪的优化,只有当变量在序列点之间没有被修改两次并且做一些非常令人惊讶的事情时才会正确),但事情可能会发生在数学上是极不可能的,因为有人故意不顾一切地让它们发生。
答案 6 :(得分:1)
在Linux
中,当您是root用户时,任何操作都有效。甚至破坏你的根文件系统。
rm -rf /
当你是root
时,每个代码段(带有错误)都会很愉快地执行。假设所有 UB 都具有sudo
权限。
答案 7 :(得分:0)
[这个答案迟了四年。谁会读它?我们将看到。]
呃,没有冒犯,但根据我的经验,其他几个答案都是错误的,或者无论如何都是误导。
C ++标准不限制未定义的行为。但是,操作系统通常会限制它。原因:对于C ++,行为未定义。
...始终要小心,永远不要调用未定义的行为。
无意义。经验掩盖了这个建议。 C ++程序员经常在测试期间无意中调用未定义的行为。有时我会故意这样做,只是为了看看会发生什么。
现在,我意识到有人认为我在这里炫耀愚蠢,但实际上,你的笔记本电脑几乎不可能因未定义而非定义的行为而着火。 C ++中未定义的行为使用定义的行为发出汇编代码。考虑一下。装配行为仍然定义。只是C ++标准不再理解这些机制。
有些时候你想引发未定义的行为只是为了看看堆栈上发生了什么。
如果您所处的环境中可以编写定义的 C ++程序,使您的笔记本电脑着火,那么无论如何您都必须小心;但在这种情况下的主要问题是缺乏基于硬件和/或内核的保护。
总之,不要让C ++标准让你感到困惑。它只是告诉你它自身的能力范围是什么。