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

C最低标准要求

  •  1
  • izac89  · 技术社区  · 6 年前

    从C标准 ISO/IEC 9899:201X标准 5.1.2.3p6 :

    对合格实施的最低要求是:

    • 对易失性对象的访问严格按照 抽象机器的规则。
    • 程序终止时,所有数据 写入文件应与执行 根据抽象语义的程序将产生。
    • 交互设备的输入和输出动态应发生。 如7.21.3所述。这些要求的目的是 无缓冲或线路缓冲输出尽快出现,以 确保提示消息实际出现在程序之前 正在等待输入。

    这是程序的可观察行为。

    这段话的意思相当戏剧化(至少对我来说是这样),在我看来,这段话说:

    (1)产生与完全符合标准的抽象机产生的相同可观察行为的编译器是符合标准的编译器,这意味着标准中的所有其他要求和段落都只是额外的(除了挥发物部分和7.21.3),例如,符合标准的编译器实际上可以打破评估顺序( a && b )只要观察到的行为(挥发物、文件内容和交互输出)是正确的。

    (2)一个没有挥发物、不写入文件、没有输入输出交互的程序,是一个实际上不做任何事情、没有可观察的行为并且可以完全优化为(例如)两条指令的程序。 xor eax, eax ret (x86-64 clang 7.0.0)在 main .

    我说得对吗?还是说得对?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Eric Postpischil    6 年前
    1. 是的,C实现(不仅仅是编译器)包括标准库、链接和运行时支持,和/或其他用于实现C的工具,它产生相同的可观察行为。 作为一个一致的抽象机器是一致的。标准中的所有其他要求和段落不仅仅是额外的。它们定义了抽象机器的行为,因此它们有助于描述可观察的行为必须是什么。

    2. 是的,没有可观察行为的程序可以优化为只返回的程序。注意 the standard does not actually include exit status in observable behavior 所以 xor eax, eax 技术上不需要。然而,这可能只是标准中的一个无意的缺陷,而不是意图。

    笔记

    程序的行为不是规范所要求的唯一事情。例如,实现还必须记录各种实现定义的行为。因此,这个假设的C实现与一些抽象机的行为相同,它还必须包含所需的文档。

        2
  •  2
  •   Serge Ballesta    6 年前

    是的,你是对的。编译器可以做它想做的, 如果观察到的行为与抽象机器所能产生的行为相同 . 但这本身并不是戏剧性的:为什么我们会关心一些不能被观察到的东西?这是优化编译器的关键。

    例子:

    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;
    }
    

    这意味着你不能使用 空的 循环来增加延迟,因为它们可以被优化,根本不会产生延迟。

    imho最显著的副作用,如果允许编译器假设程序中不会发生未定义的行为。

    第二个例子:

    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]) . 但是优化编译器可以注意到 S.B 如果不涉及UB并且可以自由地优化 S.B 阵列访问,甚至优化 b 成员。这里需要崩溃或随机值…更糟糕的是,一个非常聪明的编译器可能会注意到打印循环中存在过去绑定的访问。从这一点来看,程序行为是未定义的,例如,编译器可以在打印16 st值后停止循环。没有错误,但只打印了16个值…