当试图理解汇编(启用编译器优化)时,我看到这种行为:
这样一个非常基本的循环
outside_loop;
while (condition) {
statements;
}
Run Code Online (Sandbox Code Playgroud)
经常被编译成(伪代码)
; outside_loop
jmp loop_condition ; unconditional
loop_start:
loop_statements
loop_condition:
condition_check
jmp_if_true loop_start
; outside_loop
Run Code Online (Sandbox Code Playgroud)
但是,如果未打开优化,则会编译为通常可理解的代码:
loop_condition:
condition_check
jmp_if_false loop_end
loop_statements
jmp loop_condition ; unconditional
loop_end:
Run Code Online (Sandbox Code Playgroud)
根据我的理解,编译后的代码更像是这样的:
goto condition;
do {
statements;
condition:
}
while (condition_check);
Run Code Online (Sandbox Code Playgroud)
我看不到巨大的性能提升或代码可读性提升,为什么经常出现这种情况呢?是否有此循环样式的名称,例如"尾随条件检查"?
在 C++ 中,如果我正确理解措辞,编译器可以假设不会发生 UB,从而影响将遇到 UB 但尚未遇到的执行路径中的行为(甚至是 I/O 等可见的副作用)。
在抽象机遇到 UB 之前,C 是否需要“正确”执行程序直至最后可见的副作用?编译器似乎以这种方式运行,但在 C++ 模式和 C 模式下都是如此,因此这可能只是错过了优化,或者是有意选择减少“对程序员的敌意”。
ISO C 标准是否允许这样的优化? (出于各种原因,编译器仍然可能合理地选择不这样做,包括在不错误编译任何其他情况的情况下实现困难,或“实现质量”因素。)
这个问题(主要)是关于 C 的,但 C++ 至少是一个有趣的比较点,因为 UB 的概念在两种语言中至少是相似的。我在 ISO C 中没有看到任何类似的显式语言,因此提出了这个问题。
ISO C++ [intro.abstract]/5是这么说的(至少从 C++11 开始,可能更早):
执行格式良好的程序的一致实现应产生与具有相同程序和相同输入的抽象机的相应实例的可能执行之一相同的可观察行为。但是,如果任何此类执行包含未定义的操作,则本文档对使用该输入执行该程序的实现没有任何要求(甚至不涉及第一个未定义操作之前的操作)。
我认为对使用该输入执行该程序的实现没有要求的预期含义是,即使在抽象机遇到 UB 之前排序的可见副作用(例如访问volatile或包括 unbuffered 的 I/O fprintf(stderr, ...))也不需要即将发生。
“用该输入执行该程序”这句话是指整个程序,从执行开始就开始。(有些人谈论“时间旅行”,但这实际上是一个问题,例如后来的代码允许值范围假设(例如非空)影响编译时决策中的早期分支,正如其他人将其放在上一个SO问题。编译器可以假设整个程序的执行不会遇到UB。)
我试图让编译器来完成我想知道的优化。这非常明确地表明根据编译器开发人员对该标准的解释是允许的。(除非它实际上是一个编译器错误。)但到目前为止我所尝试的一切都表明编译器保留了可见的副作用。
我只尝试过volatile访问(不是putchar或std::cout<<或其他),假设优化器应该更容易查看和理解。对非内联函数的调用通常printf是优化器的黑匣子,除非它们是基于函数名称的特殊情况,例如一些非常重要的函数,例如memcpy. 此外,假设对 I/O 函数的调用可能会永远阻塞,甚至可能中止,因此在以后的代码中永远不会遇到 UB。
实际上我只尝试过volatile商店,没有尝试过 …
c gcc compiler-optimization undefined-behavior language-lawyer
该问题来自 cs:app3e 2.82。我了解到,当x = INT_MIN,-x也是-INT_MIN,但是
#include <stdio.h>
#include <limits.h>
int main() {
int x = INT_MIN, y = -3;
printf("%d\n", (x < y) == (-x > -y));
return 0;
}
Run Code Online (Sandbox Code Playgroud)
在我的机器上(Linux版本6.2.0-34-generic(buildd@bos03-amd64-059)(x86_64-linux-gnu-gcc-11(Ubuntu 11.4.0-1ubuntu1~22.04)11.4.0,GNU ld(GNU Binutils for Ubuntu) 2.38)),这给出输出 1。为什么会发生这种情况?
我使用gcc -o来编译它。我还使用gcc -O0来编译它。