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