在同一环境中,同一程序的编译与同一编译器之间是否需要保持一致的未指定和未定义的行为?

时间:2010-06-17 08:10:01

标签: c++ compiler-construction

让我们假装我的程序包含一个特定的构造,C ++标准声明它是未指定的行为。这基本上意味着实现必须做一些合理的事情,但不允许记录它。但是,每次编译具有未指定行为的特定构造时是否需要实现相同的行为,或者是否允许在不同的编译中产生不同的行为?

未定义的行为怎么样?让我们根据标准假装我的程序包含一个UB结构。允许实现表现出任何行为。但是,同一编译器中相同程序的编译在同一环境中具有相同设置时,此行为是否会有所不同?换句话说,如果我在文件X.cpp中的第78行取消引用空指针,并且在这种情况下驱动器的实现格式是否意味着在重新编译程序后它将执行相同的操作?

问题是......我使用相同的编译器在相同的环境中使用相同的编译器设置编译相同的程序。构造声明是未指定的行为,未定义的行为会在每次编译时产生相同的行为,还是允许它们在编译之间有所区别?

9 个答案:

答案 0 :(得分:5)

如果它是未定义的行为,那么它本质上将会发生什么是未定义的,你不能在任何情况下依赖它。

另一方面,如果语言规范中存在歧义,那么未指定的行为是由个别供应商决定如何实现的。这在编译和运行之间是一致的,但不一定在不同供应商之间。因此,例如,当您仅使用Visual Studio构建时依赖于未指定的行为,但如果您尝试将代码移植到gcc,它可能会失败或产生与您期望的不同的行为。

答案 1 :(得分:3)

未定义的行为可能在同一程序的运行之间变化,甚至在同一程序运行中执行相同代码之间也会有所不同。例如,未初始化(自动)变量的值是未定义的,然后它的实际值就是在内存中该位置发生的任何值。显然,这可能会有所不同。

编辑:

这也适用于未指定的行为。例如,函数参数的评估顺序是未指定的,因此如果它们有副作用,那么这些副作用可以按任何顺序发生。这可能会打印出“嗨!何!”或者“何!嗨!”:

f( printf("Hi!"), printf("Ho!") );

这也可能因执行而异。正如标准所说: “因此,抽象机器的实例可以为给定程序和给定输入提供多个可能的执行顺序。”不同之处在于,使用 undefined 行为时,任何事情都可能发生:计算机可能会爆炸,重新格式化磁盘或其他任何东西。如果未指定行为,则不允许计算机爆炸。

还有实现定义的行为,例如sizeof(int)的值。对于相同的编译器,它必须始终相同。

答案 2 :(得分:3)

未指定和未定义的行为不保证在已编译的程序的单独运行之间保持一致。仅这一点已经使得单独的编译之间的一致性概念完全没有意义。

此外,可能有必要补充一点,未定义的行为可以在编译阶段通过阻止程序编译来表现出来。

答案 3 :(得分:2)

  

但是这种行为可能会有所不同   编译相同的程序   具有相同设置的相同编译器   相同的环境?

  

换句话说,如果我取消引用a   文件X.cpp中第78行的空指针   并且实现格式化   在这种情况下驾驶是否意味着   程序结束后它会做同样的事情   被重新编译?

未定义行为的结果几乎总是由编译器以语言设计者未指定的方式与操作系统和/或硬件交互发出的代码引起的。因此,如果取消引用NULL指针,发生的事情实际上与编译器无关,而是取决于底层OS /硬件如何处理无效的内存访问。如果操作系统/硬件始终以一致的方式处理(例如通过陷阱),那么您可以期望UB保持一致,但这与语言或编译器无关。

答案 4 :(得分:1)

我不知道未指明的行为(但从名称来看,也许它到处都是同样的坏/坏事,只是没有人真正知道它到底是做什么的)。但对于未定义的行为,我认为这个行为可能在平台或编译器之间表现得非常不同。我在Solaris上看到过一些非常奇怪的coredump,它们在Ubuntu等上没有出现过。

答案 5 :(得分:1)

这就是将其指定为未定义的目的......这意味着无法在不同甚至相同的平台上进行会发生什么(重复测试)。

答案 6 :(得分:1)

值得注意的是,C ++标准的指定行为的实现在编译器之间并非100%相同,即使在今天也是如此。鉴于此,期望未指定或未定义的行为与编译器无关是不合理的。如果您坚持使用标准版,那么您最有可能编写可移植代码。

答案 7 :(得分:1)

不,这部分是标准中存在未定义/实现定义的行为的原因。在同一台计算机上的相同源代码的多个编译之间(例如,使用不同的优化标志),不确定未定义的行为是相同的。

委员会显然更喜欢明确定义的行为。当委员会认为某些概念存在多个实现时,就存在实现定义的行为,并且在所有情况下都没有理由更喜欢一个实现。当委员会认为在合理的实施中难以保留任何承诺时,就存在未定义的行为。

在许多情况下,未定义的行为被实现为没有检查的东西。然后行为是由操作系统决定的,如果有的话,如果它注意到的事情发生的次数低于犹太人。

例如,取消定义您不拥有的内存是未定义的。一般来说,如果你这样做,OS会杀死你的程序。然而,如果星星恰到好处地对齐,你可能设法取消引用你在C ++规则下不拥有的记忆(例如,你没有从new得到它,或者你已经delete它)但操作系统认为你拥有。有时你会遇到崩溃,有时候你只会破坏你程序中其他地方的内存,有时你会被发现而没有被发现(例如,如果内存没有被发回)。

竞赛条件被认为是未定义的,并且它们因在程序的不同运行期间不同而臭名昭着。如果您的操作系统没有注意到,每次粉碎堆栈时,您可能会得到不同的行为。

delete未定义。通常它们会导致崩溃,但事实上它们是未定义的意味着你不能依赖崩溃的东西。

答案 8 :(得分:1)

当使用不同的优化级别或使用或不使用调试模式进行编译时,许多此类行为的实现方式不同。