如何解释未知行为以了解所有新手?

时间:2010-02-10 08:51:47

标签: c++ undefined-behavior

有一些情况,C ++标准属于未定义的行为。例如,如果我使用new[]进行分配,那么尝试使用delete(不是delete[])释放未定义的行为 - anything can happen - 它可能有用,它可能会崩溃,它可能会崩溃,它可能会默默地腐败一些东西并制定一个定时问题。

解释这个任何可能发生的事情对于新手来说是非常有问题的。他们开始“证明”“这是有效的”(因为它确实适用于他们使用的C ++实现),并询问“这可能是什么问题”?我能给出什么简洁的解释会激励他们不写这样的代码?

19 个答案:

答案 0 :(得分:48)

未定义意味着明确不可靠。软件应该可靠。你不应该多说别的。

冰冻的池塘是未定义的行走表面的一个很好的例子。仅仅因为你跨过一次并不意味着你应该在你的纸质路线上添加快捷方式,特别是如果你计划四季。

答案 1 :(得分:31)

我想到了两种可能性:

  1. 你可以问他们“只是因为你可以在午夜时分在高速公路上朝相反方向行驶并继续生存,你会经常这样做吗?”

  2. 更复杂的解决方案可能是设置不同的编译器/运行环境,以向他们展示在不同情况下它是如何失败的。

答案 2 :(得分:20)

“恭喜,你已经定义了编译器对该操作的行为。我预计明天上午10点之前关于世界上存在的其他200个编译器的行为的报告将出现在我的桌面上.Don现在让我失望,你的未来看起来很有希望!“

答案 3 :(得分:14)

简单引用标准。如果他们不能接受,那么他们就不是C ++程序员。基督徒会否认圣经吗? ; - )

1.9程序执行

  1. 本国际标准中的语义描述定义了参数化的非确定性抽象机器。 [...]

  2. 抽象机器的某些方面和操作在本国际标准中描述为实现定义(例如,sizeof(int))。这些构成了抽象机器的参数。 每个实现应包括描述其在这些方面的特征和行为的文档。 [...]

  3. 抽象机器的某些其他方面和操作在本国际标准中描述为未指定(例如,评估函数参数的顺序)。 在可能的情况下,本国际标准定义了一组允许的行为。这些定义了抽象机器的非确定性方面。 [...]

  4. 本国际标准中将某些其他操作描述为 undefined (例如,取消引用空指针的效果)。 [注意:本国际标准对包含未定义行为的程序的行为没有要求。 - 尾注]

  5. 你不能比那更清楚。

答案 4 :(得分:12)

我要解释一下,如果他们没有正确地编写代码,他们的下一次绩效评估将不会是一个愉快的。这对大多数人来说是充足的“动力”。

答案 5 :(得分:3)

我喜欢这句话:

未定义的行为:它可能损坏您的文件,格式化您的磁盘或发送仇恨邮件 你的老板。

我不知道该归因于谁(可能来自Effective C++)?

答案 6 :(得分:3)

将此人变成指针。告诉他们他们是一个人类的指针,你正在调用函数'RemoveCoat'。当他们指着一个人说'RemoveCoat'时一切都很好。如果这个人没有外套,不用担心 - 我们检查一下,所有RemoveCoat确实是去掉顶层衣服(带有正式检查)。

现在如果他们指向某个地方并且他们说RemoveCoat会发生什么 - 如果他们指着墙壁那么油漆可能会剥落,如果他们指着一棵树树皮可能会掉下来,狗可能会刮胡子, USS Enterprise可能会在关键时刻降低盾牌等等!

没有办法弄清楚可能发生的情况还没有为这种情况定义行为 - 这称为未定义的行为,必须避免。

答案 7 :(得分:3)

John Woods

  

简而言之,您不能在元素尚未使用的结构上使用sizeof()   定义,如果你这样做,恶魔可能会飞出你的鼻子。

“恶魔可能会飞出你的鼻子”只是必须成为每个程序员词汇的一部分。

更重要的是,谈谈可移植性。解释程序如何经常被移植到不同的操作系统,更不用说不同的编译器了。在现实世界中,端口通常由原始程序员以外的人完成。这些端口中的一些甚至是嵌入式设备,在这些设备中,发现编译器的决定与您的假设不同,可能会产生巨大的成本。

答案 8 :(得分:3)

让他们尝试一下,直到他们的代码在测试期间崩溃。然后就不需要这些词了。

问题是,新手(我们都在那里)有一些自负和自信。没关系。事实上,如果你不这样做,你就不可能成为程序员。教育他们很重要,但同样重要的是要支持他们,不要通过削弱他们对自己的信任来减少他们在旅途中的开始。只是礼貌但用事实证明你的立场而不是言语。只有事实和证据才有效。

答案 9 :(得分:3)

悄悄地覆盖新的,新的[],删除和删除[]并查看他需要注意多长时间;)

失败了......告诉他他错了,并指出他对C ++规范。哦,是的..下次在雇用人员时要更加小心,以确保避免漏洞!

答案 10 :(得分:2)

C ++实际上并不是一种稀释剂的语言,只是简单地列出一些规则并使它们毫无疑问地服从它们会使一些可怕的程序员;我看到人们所说的大部分最愚蠢的事情可能都与这种盲目规则/律师有关。

另一方面,如果他们知道析构函数不会被调用,可能还有一些其他问题,那么他们会注意避免它。更重要的是,有机会调试它,如果他们偶然做到这一点,并且还有机会意识到C ++的许多功能有多危险。

因为有很多事情需要担心,所以没有一门课程或书籍可以让某人掌握C ++,甚至可能用它来做好。

答案 11 :(得分:1)

向他们展示Valgrind。

答案 12 :(得分:1)

一个会......

“此”用法不是该语言的一部分。如果我们要说在这种情况下编译器必须生成崩溃的代码,那么它将是一个特性,对编译器的制造商来说是某种要求。该标准的作者不想对不受支持的“功能”进行不必要的工作。在这种情况下,他们决定不做出任何行为要求。

答案 13 :(得分:1)

编译并运行此程序:

#include <iostream>

class A {
    public:
            A() { std::cout << "hi" << std::endl; }
            ~A() { std::cout << "bye" << std::endl; }
};

int main() {
    A* a1 = new A[10];
    delete a1;

    A* a2 = new A[10];
    delete[] a2;
}

至少在使用GCC时,它表明只有在执行单个删除时才会为其中一个元素调用析构函数。

关于POD阵列上的单个删除。将他们指向C++ FAQ或让他们通过cppcheck运行他们的代码。

答案 14 :(得分:1)

关于未定义行为尚未提及的一点是,如果执行某些操作会导致未定义的行为,那么符合标准的实现可以合法地,可能是为了“有用”或提高效率,生成将失败的代码尝试了这样的操作。例如,可以想象一种多处理器体系结构,其中任何存储器位置都可以被锁定,并且尝试访问锁定位置(解锁它除外)将停止,直到所述位置被解锁为止。如果锁定和解锁非常便宜(如果它们在硬件中实现则合理)这样的架构在某些多线程场景中可能很方便,因为实现x++为(原子读取并锁定x;添加一个读取value; atomically unlock和write x)将确保如果两个线程同时执行x++,结果将是向x添加两个。编写程序是为了避免未定义的行为,这样的架构可以简化可靠的多线程代码的设计,而不需要大的笨重的内存障碍。遗憾的是,如果*x++ = *y++;x都是对同一存储位置的引用,并且编译器尝试将代码管道为y,则t1 = read-and-lock x; t2 = read-and-lock y; read t3=*t1; write *t2=t3; t1++; t2++; unlock-and-write x=t1; write-and-unlock y=t2;之类的语句可能会导致死锁。虽然编译器可以通过避免交错各种操作来避免死锁,但这样做可能会妨碍效率。

答案 15 :(得分:0)

告诉他们有关标准以及如何开发工具以符合标准。超出标准的任何东西可能会或可能不会起作用,即UB。

答案 16 :(得分:0)

使用析构函数打开malloc_debug和delete一个对象数组。 free块内的指针应该失败。一起打电话给他们并证明这一点。

你需要考虑其他的例子来建立你的可信度,直到他们明白他们是新手,并且有很多关于C ++的知识。

答案 17 :(得分:0)

仅仅因为他们的程序出现才能正常工作;编译器可以生成恰好工作的代码(如果在工作日正确的行为是 undefined ?),你如何定义“工作”,但在周末格式化你的磁盘。他们是否读过编译器的源代码?检查他们的反汇编输出?

或者提醒他们只是因为它在今天发生“工作”并不能保证它在升级你的编译器版本时起作用。告诉他们好好发现从中爬出来的任何微妙的错误。

真的,为什么?他们应该提供一个合理的论据来使用未定义的行为,而不是相反。除了懒惰之外,有什么理由使用delete代替delete[]? (好的,有std::auto_ptr。但如果您使用std::auto_ptr并使用new[]分配的数组,那么您可能应该使用std::vector。)

答案 18 :(得分:0)

C和C ++标准都使用术语“未定义的行为”来表示这样的情况:对于不同的实现,以不同的,不兼容的方式处理构造可能很有用,其中某些行为可预测,但有些可能不。两者都使用相同的术语来描述UB,尽管我不知道任何已发布的C ++标准基本原理,但C标准的基本原理说:

未定义的行为授予实施者许可证,以使其不捕获某些难以诊断的程序错误。它还确定了可能的符合语言扩展的领域:实现者可以通过提供正式未定义行为的定义来扩展语言。”

请注意,在很多(如果不是全部)实现中,许多被C标准归类为“未定义行为”的动作都被认为是完全定义的,但是该标准的作者希望使面向非常规平台或应用程序领域的实现者能够偏离C。正常的行为,如果这样做会使他们的客户受益。这种自由并不是要引起先例的任意和反复无常的偏离,从而使程序员更难于快速,轻松地完成需要做的事情。

不幸的是,许多使用gcc和clang的程序员并不理解他们的需求,也不了解这些编译器的维护者,他们认识到,由于该标准避免了强制执行任何会损害应用程序效率的事情,因此,这些应用程序永远不会受到恶意的攻击,精心设计的输入,或仅在即使恶意程序也无法破坏任何东西的环境中运行,这意味着不需要任何实现即可允许程序员轻松高效地编写适合在其他环境中使用的程序。