Kyl*_*and 93 c c++ undefined-behavior language-lawyer
编辑:这个问题并不是一个讨论未定义行为的(de)优点的论坛,但这就是它的变化.在任何情况下,这个关于假设的C编译器没有未定义行为的线程可能对那些认为这是一个重要主题的人更感兴趣.
当然,"未定义行为"的经典伪装例子是"鼻子恶魔" - 物理上是不可能的,无论C和C++标准允许什么.
因为C和C++社区倾向于强调未定义行为的不可预测性以及允许编译器在遇到未定义行为时使程序完全做任何事情的想法,所以我假设标准没有任何限制关于行为,以及未定义的行为.
[C++14: defns.undefined]:[..]允许的未定义行为包括完全忽略不可预测的结果,在转换或程序执行期间以环境特征(有或没有发出诊断消息)的文档方式执行,终止转换或执行(发布诊断信息).[..]
这实际上指定了一小组可能的选项:
我假设在大多数情况下,编译器选择忽略未定义的行为; 例如,当读取未初始化的内存时,可能是插入任何代码以确保一致行为的反优化.我认为陌生人类型的未定义行为(例如" 时间旅行 ")将属于第二类 - 但这需要记录这些行为并"环境特征"(所以我猜鼻腔恶魔只能由地狱计算机?).
我误解了这个定义吗?这些仅仅是可能构成未定义行为的例子,而不是一个全面的选项列表吗?"任何可能发生的事情"的说法仅仅意味着忽视这种情况的意外副作用吗?
编辑:两个小问题澄清:
Meh*_*dad 77
是的,它允许任何事情发生.这个说明只是举例.定义很清楚:
未定义的行为:本国际标准没有要求的行为.
sup*_*cat 23
未定义行为的历史目的之一是允许某些行为可能在不同平台上具有不同的潜在有用效果.例如,在C的早期,给出
int i=INT_MAX;
i++;
printf("%d",i);
Run Code Online (Sandbox Code Playgroud)
一些编译器可以保证代码将打印某些特定值(对于二进制补码机器,它通常是INT_MIN),而其他编译器可以保证程序在没有到达printf的情况下终止.根据应用要求,任一行为都可能有用.保持行为未定义意味着异常程序终止是溢出的可接受结果,但产生看似有效但错误的输出的应用程序不会,如果在可靠地捕获它的平台上运行则可以放弃溢出检查,以及应用程序在溢出的情况下异常终止是不可接受的,但产生算术不正确的输出,如果在没有捕获溢出的平台上运行,可能会放弃溢出检查.
然而,最近,一些编译器作者似乎已经参加了一场竞赛,看谁能最有效地消除任何代码,而这些代码的存在不会被标准规定.例如,鉴于......
#include <stdio.h>
int main(void)
{
int ch = getchar();
if (ch < 74)
printf("Hey there!");
else
printf("%d",ch*ch*ch*ch*ch);
}
Run Code Online (Sandbox Code Playgroud)
超现代编译器可以得出结论,如果ch是74或更大,计算ch*ch*ch*ch*ch将产生未定义的行为,因此程序应该打印"嘿那里!" 无条件地无论输入什么字符.
小智 16
挑剔:你没有引用标准.
这些是用于生成C++标准草稿的来源.这些来源不应被视为ISO出版物,除非C++工作组(ISO/IEC JTC1/SC22/WG21)正式采用,否则也不应将这些来源视为ISO出版物.
解释:根据ISO/IEC指令第2部分,注释不是规范性的.
集成在文档文本中的注释和示例仅用于提供有助于理解或使用文档的其他信息.它们不应包含要求("应";见3.3.1和表H.1)或任何被认为对文件使用必不可少的信息,例如说明(命令性;见表H.1),建议("应该";见3.3.2和表H.2)或许可("可";见表H.3).注释可以写成事实陈述.
强调我的.仅此就排除了"综合选项列表".然而,给出的例子确实算作"旨在帮助理解文件的附加信息".
请记住,"鼻妖"模因并不是字面意思,就像使用气球来解释宇宙膨胀如何在物理现实中保持真理一样.这是为了说明当允许做任何事情时讨论"未定义的行为" 应该做什么是蛮干的.是的,这意味着外太空没有实际的橡皮筋.
Pet*_*ter 12
在每个C和C++标准中,未定义行为的定义基本上是标准对发生的事情没有要求.
是的,这意味着任何结果都是允许的.但是没有特定的结果需要发生,也没有任何结果不需要发生.如果你有一个编译器和库一致地产生一个特定行为以响应特定的未定义行为实例 - 这样的行为不是必需的,并且即使在编译器的未来错误修复版本中也可能会改变 - 并且编译器也没关系根据C和C++标准的每个版本,仍然完全正确.
如果您的主机系统具有与插入到您的鼻孔中的探针的连接形式的硬件支持,那么发生未定义的行为可能会导致不希望的鼻效应.
我以为我只会回答你的一个观点,因为其他答案很好地回答了一般性问题,但是还没有解决这个问题.
"忽略这种情况 - 是的,标准继续说这将产生"不可预测的结果",但这与编译器插入代码不同(我认为这是一个先决条件,你知道,鼻子恶魔). "
在没有编译器插入任何代码的情况下,使用合理的编译器可以非常合理地预期会出现鼻子恶魔的情况如下:
if(!spawn_of_satan)
printf("Random debug value: %i\n", *x); // oops, null pointer deference
nasal_angels();
else
nasal_demons();
Run Code Online (Sandbox Code Playgroud)
如果编译器可以证明*x是空指针解除引用,则完全有权作为某些优化的一部分,说"好的,所以我看到他们在if的这个分支中取消引用了一个空指针.因此,作为该分支的一部分,我可以做任何事情.因此我可以对此进行优化:"
if(!spawn_of_satan)
nasal_demons();
else
nasal_demons();
Run Code Online (Sandbox Code Playgroud)
"从那里,我可以优化到这个:"
nasal_demons();
Run Code Online (Sandbox Code Playgroud)
您可以看到,在适当的情况下,这种事情如何证明对优化编译器非常有用,但却会造成灾难.我确实在一段时间后看到了一些例子,其中实际上优化能够优化这种情况非常重要.当我有更多时间时,我可能会尝试将它们挖出来.
编辑:一个例子来自我对这种优化有用的情况的记忆深度,你经常检查一个指针是否为NULL(可能是内联辅助函数),即使已经取消引用它并且没有改变了 优化编译器可以看到你已经取消引用它并因此优化了所有"is NULL"检查,因为如果你已经取消引用它并且它是null,则允许发生任何事情,包括不运行"is NULL"检查.我相信类似的论点适用于其他未定义的行为.
首先,需要注意的是,不仅未定义用户程序的行为,而且未定义编译器的行为.类似地,UB在运行时不会遇到,它是源代码的属性.
对于编译器编写者来说,"行为未定义"意味着"你不必考虑这种情况",甚至"你可以假设没有源代码会产生这种情况".当与UB一起呈现时,编译器可以有意或无意地执行任何操作,并且仍然符合标准,所以是的,如果您授予了访问权限......
然后,并不总是能够知道程序是否具有UB.例:
int * ptr = calculateAddress();
int i = *ptr;
Run Code Online (Sandbox Code Playgroud)
知道这是否可能是UB将需要知道返回的所有可能值calculateAddress(),这在一般情况下是不可能的(参见" 暂停问题 ").编译器有两种选择:
ptr将始终具有有效地址第一个选项产生快速程序,并为程序员提供避免不良影响的负担,而第二个选项产生更安全但速度更慢的代码.
C和C++标准使这个选择开放,大多数编译器选择第一个,而Java例如强制要求第二个.
为什么行为不是实现定义的,而是未定义的?
实现定义装置(N4296,1.9§2):
抽象机的某些方面和操作在本国际标准中描述为实现定义的(例如,sizeof(int)).这些构成了抽象机器的参数.每个实施应包括描述其在这些方面的特征和行为的文件.此类文档应定义与该实现相对应的抽象机器的实例(下面称为"相应实例").
强调我的.换句话说:当源代码使用实现定义的功能时,编译器编写者必须准确记录机器代码的行为.
写入随机非空无效指针是您可以在程序中执行的最不可预测的事情之一,因此这也需要降低性能的运行时检查.
在我们拥有MMU之前,您可以通过写入错误的地址来破坏硬件,这非常接近于鼻子恶魔;-)
| 归档时间: |
|
| 查看次数: |
6796 次 |
| 最近记录: |