根据C标准 ISO / IEC 9899:201x 5.1.2.3p6 :
符合标准的实现的最低要求是:
- 严格根据以下内容评估对易失对象的访问 抽象机器的规则。
- 程序终止时,所有数据 写入文件应与执行以下命令的结果相同 根据抽象语义编写的程序。
- 交互式设备的输入和输出动态应发生 如7.21.3所述。这些要求的目的是 无缓冲或行缓冲的输出会尽快出现,以 确保提示消息实际上出现在程序之前 等待输入。
这是程序的可观察行为。
本段的意思是戏剧性的(至少对我而言),以我的看法,该段说:
(1)与完全符合标准的抽象机所产生的行为相同的可观察行为的编译器是符合标准的编译器,这意味着标准中的所有其他要求和段落都只是多余的(除了volatile和7.21.3节),例如,只要可观察到的行为(volatile,文件内容和交互式输出)正确,那么符合标准的编译器实际上可以破坏评估顺序规则(a && b
)。
(2)没有挥发性的程序,不写文件,也没有输入输出交互的程序,实际上是什么也不做,没有可观察到的行为,可以完全优化为例如两个{{1}中的xor eax, eax
指令比{{1}中的ret
(x86-64 clang 7.0.0)。
我是对的还是不对吗?
答案 0 :(得分:2)
是的,C的实现(不仅仅是编译器,包括标准库,链接和运行时支持,和/或用于实现C的其他任何东西),它产生相同的可观察到的行为 1 作为一致的抽象机是一致的。标准中的所有其他要求和段落不只是多余的。它们定义了抽象机的行为,因此有助于描述可观察到的行为。
是的,可以将无可观察行为的程序优化为仅返回的程序。请注意,the standard does not actually include exit status in observable behavior是xor eax, eax
的技术要求。但是,这很可能只是标准中的一个疏忽缺陷,而不是意图。
1 程序的行为并不是规范要求的唯一内容。例如,实现还必须记录各种实现定义的行为。因此,这种行为与某些抽象机相同的假设C实现也必须包含必需的文档。
答案 1 :(得分:2)
是的,您是对的。 只要可观察到的行为与抽象机可能产生的行为相同,编译器就可以执行所需的操作。但是,这本身并不引人注目:为什么我们要关心那些无法观察到的东西?这就是优化编译器的重点。
示例:
int main() {
int a;
for (int i=INT_MAX; i>=0; i--) {
a = i;
}
printf("%d\n", a);
return 0;
}
唯一可观察到的行为,它将一次打印0
。因此,编译器可以优化循环以产生与以下内容相同的代码:
int main() {
printf("%d\n", 0);
return 0;
}
从本质上讲,您不能使用 empty 循环添加延迟,这是因为可以对其进行优化,使其完全不产生延迟。
恕我直言,如果允许编译器假定程序中不会发生未定义的行为,则是最严重的副作用。
第二个示例:
int main() {
struct {
int a[16];
int b[16];
} s;
for (i=0; i<16; i++) {
s.a[i] = i;
s.b[i] = 2 * i;
}
for (i=0; i<32; i++) {
printf(" %d", s.a[i]); // UB array access past upper bound
}
printf("\n");
return 0;
}
天真的编译器应该显示0到31之间的所有数字,因为我们知道数组s.a
和s.b
应该相邻,并且指针算术应该给出&(s.b[0]) == &(s.a[16])
。但是,优化的编译器可以注意到,如果不涉及UB,s.b
值将不会用于可观察的行为,并且可以自由地优化s.b
数组访问,甚至可以优化b
会员。此处可能会出现崩溃或随机值的情况。更糟糕的是,一个非常聪明的编译器可能会注意到打印循环中存在绑定访问。因此,程序的行为是不确定的,例如,编译器可以在打印第16个值后停止循环。没有错误,但只打印了16个值...