代码之家  ›  专栏  ›  技术社区  ›  sharptooth

如何解释未定义的行为来了解所有的新手?

  •  34
  • sharptooth  · 技术社区  · 15 年前

    这里有少数情况,C++标准的属性是未定义的行为。例如,如果我分配 new[] ,然后尝试释放 delete (不是) delete[] )这是未定义的行为- anything can happen -它可能会工作,它可能会令人讨厌地崩溃,它可能会悄悄地破坏某些东西,并设置一个定时问题。

    解释这个问题太麻烦了 任何事情都可能发生 部分给新手。他们开始“证明”这是“工作”(因为它真的适用于他们使用的C++实现),并问“这有什么可能出错”?我能给出什么简明的解释来激励他们不写这样的代码?

    18 回复  |  直到 13 年前
        1
  •  18
  •   Ignacio Vazquez-Abrams    15 年前

    “恭喜您,您已经为该操作定义了编译器的行为。我希望明天上午10点之前,世界展览中其他200名编者的行为报告会在我的办公桌上。别让我失望了,你的未来看起来很有希望!”

        2
  •  45
  •   Alex Neth    15 年前

    未定义意味着显式不可靠。软件应可靠。你不必说太多。

    一个结冰的池塘是一个未定义行走表面的好例子。仅仅因为你能穿越一次,并不意味着你应该为你的纸张路线添加捷径,特别是如果你计划在四个季节。

        3
  •  31
  •   Péter Török    15 年前

    我想到两种可能性:

    1. 你可以问他们,“只是因为你可以在午夜在高速公路上反方向行驶并存活下来,你会经常这样做吗?”

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

        4
  •  13
  •   fredoverflow    15 年前

    只需引用标准。如果他们不能接受这一点,他们就不是C++程序员。基督徒会否认圣经吗?;-)

    1.9程序执行

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

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

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

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

    你再清楚不过了。

        5
  •  12
  •   anon    15 年前

    我会解释说,如果他们没有正确地编写代码,他们下一次的性能评估将不会是一个快乐的。对大多数人来说,这是足够的“动机”。

        6
  •  3
  •   user151323    15 年前

    让他们尝试自己的方法,直到他们的代码在测试期间崩溃。那就不需要这些词了。

    问题是,新手(我们都在那里)有一定的自我和自信。没关系。事实上,如果你没有,你就不能成为一个程序员。教育他们很重要,但同样重要的是支持他们,不要因为破坏他们对自己的信任而中断他们的旅程。只是要有礼貌,但要用事实来证明你的立场,而不是用言语。只有事实和证据有效。

        7
  •  3
  •   Goz    15 年前

    悄悄地重写new、new[]、delete和delete[]并查看他需要多长时间才能注意到;)

    失败了…只要告诉他他错了,把他指向C++。哦,是的。下一次雇佣员工时要更加小心,以确保避免出现A洞!

        8
  •  3
  •   Manuel    15 年前

    我喜欢这句话:

    未定义的行为:它可能会损坏文件、格式化磁盘或向 你的老板。

    我不知道该把这个归因于谁(也许是因为 Effective C++ )?

        9
  •  3
  •   JXG Jasmynn Flores    15 年前

    John Woods :

    简而言之,不能对元素尚未 定义,如果你这样做,恶魔可能会飞出你的鼻子。

    “恶魔可能会从你的鼻子里飞出来”,这只是每个程序员词汇的一部分。

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

        10
  •  3
  •   Stefan    13 年前

    把这个人变成一个指针。告诉他们它们是指向类human的指针,而您正在调用函数“removecoat”。当他们指着一个人说“重新坐船”时,一切都很好。如果这个人没有外套,不用担心——我们检查一下,所有的脱衣工作都是脱掉衣服的顶层(有礼貌的检查)。

    现在,如果他们随意地指向某个地方,他们说“重新覆盖”——如果他们指向一堵墙,那么油漆可能会剥落,如果他们指向一棵树,树皮可能会脱落,狗可能会自己刮胡子,USS企业号可能会在关键时刻降下护盾等等!

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

        11
  •  2
  •   Charles Eli Cheese    15 年前

    C++并不是一个真正适用于懒惰者的语言,简单地列出一些规则并使它们毫无疑问地遵从会对一些可怕的程序员造成影响;我所看到的大多数人所说的最愚蠢的事情可能与这种盲目的遵循/律师的规则有关。

    另一方面,如果他们知道析构函数不会被调用,可能还有其他问题,那么他们会小心避免它。更重要的是,如果他们偶然做了这件事,就有机会调试它,同时也有机会意识到C++的许多特性是多么危险。

    因为有很多事情需要担心,没有一门课程或书会让某人掌握C++,或者甚至可能成为它的好东西。

        12
  •  1
  •   Notinlist    15 年前

    一个是…

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

        13
  •  1
  •   Piotr Justyna    15 年前

    让他们看看瓦尔格里德。

        14
  •  1
  •   Johan Kotlinski    15 年前

    编译并运行此程序:

    #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 .

        15
  •  1
  •   supercat    14 年前

    关于未定义的行为还没有提到的一点是,如果执行某些操作会导致未定义的行为,那么符合标准的实现可以合法地生成代码,也许是为了“有帮助”或提高效率,如果尝试这样的操作,代码将失败。例如,可以想象一个多处理器体系结构,其中任何内存位置都可能被锁定,并且试图访问锁定位置(解锁位置除外)将暂停,直到相关位置被解锁。如果锁定和解锁是非常便宜的(如果在硬件中实现的话是合理的),那么这样的体系结构在一些多线程场景中是很方便的,因为实现 x++ as(自动读取和锁定x;向读取值添加一个;自动解锁和写入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; . 虽然编译器可以通过避免交叉操作来避免死锁,但这样做可能会妨碍效率。

        16
  •  0
  •   Potatoswatter    15 年前

    打开malloc_调试并 delete 带有析构函数的对象数组。 free 在块中使用指针应该失败。把他们召集在一起,演示一下。

    你需要考虑其他的例子来建立你的信誉,直到他们知道他们是新手,还有很多关于C++的知识。

        17
  •  0
  •   piotr    15 年前

    告诉他们有关标准以及如何开发工具以符合标准。标准之外的任何东西都可能或可能不起作用,这就是ub。

        18
  •  0
  •   jamesdlin    15 年前

    只是因为他们的计划 出现 工作是什么都不能保证的;编译器可以生成正常工作的代码(当正确的行为是 未定义 ?)在工作日,但在周末格式化磁盘。他们把源代码读给编译器了吗?检查分解后的输出?

    或者提醒他们,仅仅因为它今天发生在“工作”上,并不能保证它在您升级编译器版本时能正常工作。告诉他们找到从中爬出来的任何细微的虫子都很有趣。

    真的,为什么 是吗?他们应该提供一个合理的理由来使用未定义的行为,而不是相反。有什么理由用 delete 而不是 delete[] 除了懒惰?好吧,有 std::auto_ptr . 但是如果你用 标准:自动测试 用一个 new[] -分配的数组,您可能应该使用 std::vector 不管怎样)