pmor Asked: 2024-08-23 00:16:48 +0800 CST2024-08-23 00:16:48 +0800 CST 2024-08-23 00:16:48 +0800 CST “执行包含未定义的操作”实际上是什么意思? 772 C++(n4917.pdf),4.1.2p5(重点添加): 符合规范的实现执行格式良好的程序时,应产生与使用相同程序和相同输入执行相应抽象机实例的可能执行之一相同的可观察行为。但是,如果任何此类执行包含未定义的操作,则本文档对使用该输入执行该程序的实现不做任何要求(即使对于第一个未定义操作之前的操作也不做要求)。 “执行包含未定义的操作”实际上是什么意思? 它是关于未定义操作的存在(即没有执行)还是关于未定义操作的执行? c++ 3 个回答 Voted Best Answer user17732522 2024-08-23T01:24:22+08:002024-08-23T01:24:22+08:00 抽象机由标准中声明为实现定义的方面参数化,参见[intro.abstract]/2。 实现定义行为的每个实现都会实现抽象机器的一个实例。 抽象机的每个此类实例都可以针对具有任何给定输入的任何程序运行,并且是非确定性的。其非确定性方面是标准规定为未指定的行为。然后,所有未指定行为的每个实现都会针对给定的程序和输入确定抽象机实例上的一次确定性执行,这些执行是抽象机根据标准其余部分中指定的程序执行规则在给定该程序和输入的实现定义和未指定行为的实现的情况下执行的操作。请参阅[intro.abstract]/3。 您的引文指出,给定 C++ 程序和任何特定的程序输入实现,如果任何执行(即未指定行为的实现)将导致执行具有未定义行为的操作,则编译器不必保证该程序针对该输入的任何特定行为。 否则,编译器必须确保具有给定输入的程序的可观察行为对应于至少一次执行(即未指定行为的实现)在抽象机实例上的可观察行为。 所有这些都是在谈论抽象机器上的执行,而不是编译代码的实际执行。观点是外部的,也是时间维度的。在确定编译器是否需要保证任何特定行为时,我们会查看整个执行过程中对程序的所有输入。如果程序在整个执行时间内对给定的一组输入在一次未指定行为的实现中具有未定义的行为,那么对可观察行为没有任何要求,即使在程序执行开始时,如果这些输入会发生。 当然,实际上用户输入并不是确定性的,我们无法预测未来的输入,因此只要不会导致未定义行为的输入是可能的,我们就必须确保程序能够为此类输入产生有效的可观察行为。 但是,如果我们可以在某个时候确定程序执行对于任何可能的未来输入集都将具有未定义的行为,而每个此类输入实现都会产生一个未指定行为的实现,那么就不再需要产生任何特定的可观察行为,即使在发生未定义操作或导致未定义操作的用户输入之前,天真地遵循抽象机器的规则进行正在进行的执行也会产生进一步的可观察行为。 bolov 2024-08-23T04:37:44+08:002024-08-23T04:37:44+08:00 已经有一个很好的答案了。我只是想举一个具体的例子来澄清一下。 鉴于这个程序: #include <iostream> int ub() { int* bad = nullptr; return *bad; } int main() { int input; std::cout << "Hello" << std::endl; std::cin >> input; std::cout << "Point A" << std::endl; if (input == 42) { std::cout << "Point B" << std::endl; ub(); std::cout << "Point C" << std::endl; } else { std::cout << "Point D" << std::endl; } std::cout << "Bye!" << std::endl; } 🛈 从现在开始我谈论运行程序,在具有指定输入的物理机器上执行程序。 对于任何整数输入,42程序都必须表现出这种精确的行为: 打印到标准输出: Hello Point A Point B Point C Point D Bye 终止(也返回0给调用者) 对于整数输入,42程序表现出未定义行为。因此,它没有定义的行为。它不需要输出任何特定内容(如果有的话),不需要终止,它可以崩溃等。 更清楚的是:在执行 UB 操作之前,不需要输出Point A正在执行的操作。Point B 此外,即使在执行时"std::cout << "Hello" << std::endl;"还不知道程序的执行是否为 UB,但理论上如果输入是,则42程序不需要输出Hello。 当然,实际上程序会输出Hello。但是,如果缺少该行<< std::endl,则不会刷新该行,因为42在实际输入程序运行时,可能Hello不会打印 。 pmor 2024-08-23T02:01:35+08:002024-08-23T02:01:35+08:00 正如用户 Raymond Chen所评论的,我确实混淆了抽象机器的执行与具体(例如物理)机器的执行。 当询问(和评论)时,我想到的是具体的(例如物理的)机器。 据我了解,如果编译器证明程序包含未定义操作(可执行,即非“死代码”),那么这种未定义操作的执行就已经发生(在翻译时,在“抽象机器级别”),这是潜在(整个程序)无效的触发器(例如,跳过/取消/更改第一个未定义操作之前的操作)。
抽象机由标准中声明为实现定义的方面参数化,参见[intro.abstract]/2。
实现定义行为的每个实现都会实现抽象机器的一个实例。
抽象机的每个此类实例都可以针对具有任何给定输入的任何程序运行,并且是非确定性的。其非确定性方面是标准规定为未指定的行为。然后,所有未指定行为的每个实现都会针对给定的程序和输入确定抽象机实例上的一次确定性执行,这些执行是抽象机根据标准其余部分中指定的程序执行规则在给定该程序和输入的实现定义和未指定行为的实现的情况下执行的操作。请参阅[intro.abstract]/3。
您的引文指出,给定 C++ 程序和任何特定的程序输入实现,如果任何执行(即未指定行为的实现)将导致执行具有未定义行为的操作,则编译器不必保证该程序针对该输入的任何特定行为。
否则,编译器必须确保具有给定输入的程序的可观察行为对应于至少一次执行(即未指定行为的实现)在抽象机实例上的可观察行为。
所有这些都是在谈论抽象机器上的执行,而不是编译代码的实际执行。观点是外部的,也是时间维度的。在确定编译器是否需要保证任何特定行为时,我们会查看整个执行过程中对程序的所有输入。如果程序在整个执行时间内对给定的一组输入在一次未指定行为的实现中具有未定义的行为,那么对可观察行为没有任何要求,即使在程序执行开始时,如果这些输入会发生。
当然,实际上用户输入并不是确定性的,我们无法预测未来的输入,因此只要不会导致未定义行为的输入是可能的,我们就必须确保程序能够为此类输入产生有效的可观察行为。
但是,如果我们可以在某个时候确定程序执行对于任何可能的未来输入集都将具有未定义的行为,而每个此类输入实现都会产生一个未指定行为的实现,那么就不再需要产生任何特定的可观察行为,即使在发生未定义操作或导致未定义操作的用户输入之前,天真地遵循抽象机器的规则进行正在进行的执行也会产生进一步的可观察行为。
已经有一个很好的答案了。我只是想举一个具体的例子来澄清一下。
鉴于这个程序:
🛈 从现在开始我谈论运行程序,在具有指定输入的物理机器上执行程序。
对于任何整数输入,
42
程序都必须表现出这种精确的行为:0
给调用者)对于整数输入,
42
程序表现出未定义行为。因此,它没有定义的行为。它不需要输出任何特定内容(如果有的话),不需要终止,它可以崩溃等。更清楚的是:在执行 UB 操作之前,不需要输出
Point A
正在执行的操作。Point B
此外,即使在执行时
"std::cout << "Hello" << std::endl;"
还不知道程序的执行是否为 UB,但理论上如果输入是,则42
程序不需要输出Hello
。当然,实际上程序会输出
Hello
。但是,如果缺少该行<< std::endl
,则不会刷新该行,因为42
在实际输入程序运行时,可能Hello
不会打印 。正如用户 Raymond Chen所评论的,我确实混淆了抽象机器的执行与具体(例如物理)机器的执行。
当询问(和评论)时,我想到的是具体的(例如物理的)机器。
据我了解,如果编译器证明程序包含未定义操作(可执行,即非“死代码”),那么这种未定义操作的执行就已经发生(在翻译时,在“抽象机器级别”),这是潜在(整个程序)无效的触发器(例如,跳过/取消/更改第一个未定义操作之前的操作)。