在C和c++中,什么是未定义的行为(UB) ?未指定的行为和实现定义的行为呢?它们之间的区别是什么?


当前回答

实现定义的,

实现者希望,应该有良好的文档,标准给出了选择,但一定要编译

未指定的,

与实现定义的相同,但没有文档化

未定义的,

不管发生什么事,都要小心。

其他回答

未定义行为是C和c++语言中让来自其他语言的程序员感到惊讶的方面之一(其他语言试图更好地隐藏它)。基本上,即使许多c++编译器不会报告程序中的任何错误,也有可能编写出行为不符合可预测方式的c++程序!

让我们来看一个经典的例子:

#include <iostream>

int main()
{
    char* p = "hello!\n";   // yes I know, deprecated conversion
    p[0] = 'y';
    p[5] = 'w';
    std::cout << p;
}

变量p指向字符串字面量“hello!”\n”,下面的两个赋值尝试修改该字符串字面量。这个程序是做什么的?根据c++标准第2.14.5节第11段,它调用未定义的行为:

尝试修改字符串文字的效果未定义。

I can hear people screaming "But wait, I can compile this no problem and get the output yellow" or "What do you mean undefined, string literals are stored in read-only memory, so the first assignment attempt results in a core dump". This is exactly the problem with undefined behavior. Basically, the standard allows anything to happen once you invoke undefined behavior (even nasal demons). If there is a "correct" behavior according to your mental model of the language, that model is simply wrong; The C++ standard has the only vote, period.

未定义行为的其他例子包括访问超出其边界的数组,对空指针进行解引用,在对象的生命周期结束后访问对象,或者编写像i++ + ++i这样据称很聪明的表达式。

c++标准的1.9节还提到了未定义行为的两个危险较小的兄弟,未指定行为和实现定义行为:

The semantic descriptions in this International Standard define a parameterized nondeterministic abstract machine. Certain aspects and operations of the abstract machine are described in this International Standard as implementation-defined (for example, sizeof(int)). These constitute the parameters of the abstract machine. Each implementation shall include documentation describing its characteristics and behavior in these respects. Certain other aspects and operations of the abstract machine are described in this International Standard as unspecified (for example, order of evaluation of arguments to a function). Where possible, this International Standard defines a set of allowable behaviors. These define the nondeterministic aspects of the abstract machine. Certain other operations are described in this International Standard as undefined (for example, the effect of dereferencing the null pointer). [ Note: this International Standard imposes no requirements on the behavior of programs that contain undefined behavior. —end note ]

具体来说,第1.3.24节指出:

允许的未定义行为的范围从完全忽略带有不可预测结果的情况,到在翻译或程序执行过程中以具有环境特征的文档化方式行事(有或没有发出诊断消息),到终止翻译或执行(发出诊断消息)。

你能做些什么来避免遇到未定义的行为?基本上,你必须阅读优秀的c++书籍,作者都知道他们在谈论什么。避免网上教程。避免bullschildt。

Historically, both Implementation-Defined Behavior and Undefined Behavior represented situations in which the authors of the Standard expected that people writing quality implementations would use judgment to decide what behavioral guarantees, if any, would be useful for programs in the intended application field running on the intended targets. The needs of high-end number-crunching code are quite different from those of low-level systems code, and both UB and IDB give compiler writers flexibility to meet those different needs. Neither category mandates that implementations behave in a way that's useful for any particular purpose, or even for any purpose whatsoever. Quality implementations that claim to be suitable for a particular purpose, however, should behave in a manner befitting such purpose whether the Standard requires it or not.

The only difference between Implementation-Defined Behavior and Undefined Behavior is that the former requires that implementations define and document a consistent behavior even in cases where nothing the implementation could possibly do would be useful. The dividing line between them is not whether it would generally be useful for implementations to define behaviors (compiler writers should define useful behaviors when practical whether the Standard requires them to or not) but whether there might be implementations where defining a behavior would be simultaneously costly and useless. A judgment that such implementations might exist does not in any way, shape, or form, imply any judgment about the usefulness of supporting a defined behavior on other platforms.

不幸的是,自20世纪90年代中期以来,编译器作者开始将缺乏行为授权解释为一种判断,即行为保证不值得付出代价,即使在它们至关重要的应用领域,甚至在它们几乎不花费任何成本的系统上。编译器编写者不再将UB视为进行合理判断的邀请,而是开始将其视为不这样做的借口。

例如,给定以下代码:

int scaled_velocity(int v, unsigned char pow)
{
  if (v > 250)
    v = 250;
  if (v < -250)
    v = -250;
  return v << pow;
}

两个互补的实现不需要花费任何精力 将表达式v << pow视为二补移位 不管v是正还是负。

The preferred philosophy among some of today's compiler writers, however, would suggest that because v can only be negative if the program is going to engage in Undefined Behavior, there's no reason to have the program clip the negative range of v. Even though left-shifting of negative values used to be supported on every single compiler of significance, and a large amount of existing code relies upon that behavior, modern philosophy would interpret the fact that the Standard says that left-shifting negative values is UB as implying that compiler writers should feel free to ignore that.

未定义行为vs.未指定行为有一个简短的描述。

他们最后的总结是:

总而言之,未指定的行为通常是不应该发生的 担心,除非你的软件被要求是可移植的。 相反,未定义的行为总是不受欢迎的,也不应该这样做 发生。

也许简单的措辞比严格的标准定义更容易理解。

实现定义的行为: 语言说我们有数据类型。编译器供应商指定了他们应该使用的大小,并提供了他们所做的工作的文档。

未定义的行为: 你做错了什么。例如,int型中有一个非常大的值,但不适合char型。如何将该值放入char?其实没有办法!任何事情都可能发生,但最明智的做法是将int的第一个字节放入char类型。这样做分配第一个字节是错误的,但这是在引擎盖下发生的事情。

未指明的行为: 这两个函数哪个先执行?

void fun(int n, int m);

int fun1() {
    std::cout << "fun1";
    return 1;
}
int fun2() {
    std::cout << "fun2";
    return 2;
}

//...

fun(fun1(), fun2()); // which one is executed first?

语言没有指定计算值,从左到右还是从右到左!因此,未指定的行为可能会导致未定义的行为,也可能不会导致未定义的行为,但可以肯定的是,您的程序不应该产生未指定的行为。


@eSKay我认为你的问题值得编辑答案以澄清更多:)

For fun(fun1(), fun2());行为不是“实现定义的”吗?编译器必须选择一个或另一个过程,毕竟?

The difference between implementation-defined and unspecified, is that the compiler is supposed to pick a behavior in the first case but it doesn't have to in the second case. For example, an implementation must have one and only one definition of sizeof(int). So, it can't say that sizeof(int) is 4 for some portion of the program and 8 for others. Unlike unspecified behavior, where the compiler can say: "OK I am gonna evaluate these arguments left-to-right and the next function's arguments are evaluated right-to-left." It can happen in the same program, that's why it is called unspecified. In fact, C++ could have been made easier if some of the unspecified behaviors were specified. Take a look here at Dr. Stroustrup's answer for that:

It is claimed that the difference between what can be produced giving the compiler this freedom and requiring "ordinary left-to-right evaluation" can be significant. I'm unconvinced, but with innumerable compilers "out there" taking advantage of the freedom and some people passionately defending that freedom, a change would be difficult and could take decades to penetrate to the distant corners of the C and C++ worlds. I am disappointed that not all compilers warn against code such as ++i+i++. Similarly, the order of evaluation of arguments is unspecified. IMO far too many "things" are left undefined, unspecified, that's easy to say and even to give examples of, but hard to fix. It should also be noted that it is not all that difficult to avoid most of the problems and produce portable code.

来自官方的C基本原理文档

The terms unspecified behavior, undefined behavior, and implementation-defined behavior are used to categorize the result of writing programs whose properties the Standard does not, or cannot, completely describe. The goal of adopting this categorization is to allow a certain variety among implementations which permits quality of implementation to be an active force in the marketplace as well as to allow certain popular extensions, without removing the cachet of conformance to the Standard. Appendix F to the Standard catalogs those behaviors which fall into one of these three categories. Unspecified behavior gives the implementor some latitude in translating programs. This latitude does not extend as far as failing to translate the program. Undefined behavior gives the implementor license not to catch certain program errors that are difficult to diagnose. It also identifies areas of possible conforming language extension: the implementor may augment the language by providing a definition of the officially undefined behavior. Implementation-defined behavior gives an implementor the freedom to choose the appropriate approach, but requires that this choice be explained to the user. Behaviors designated as implementation-defined are generally those in which a user could make meaningful coding decisions based on the implementation definition. Implementors should bear in mind this criterion when deciding how extensive an implementation definition ought to be. As with unspecified behavior, simply failing to translate the source containing the implementation-defined behavior is not an adequate response.