代码之家  ›  专栏  ›  技术社区  ›  St.Antario

两个连续的语句是否排列在一起?

  •  1
  • St.Antario  · 技术社区  · 2 年前

    我正在阅读C标准中与多线程执行相关的一部分,并对 在之前排序 用于定义 线程间发生在 :

    5.1.2.4/16 :

    A 线程间发生在 B 如果为了一些行动 X

    A. 之前已排序 十、 十、 线程间发生在 B


    起初我在想,如果采取行动 A. 先于行动 B 按照程序顺序 A. 之前排序 B ,但请考虑以下简单示例:

    int read_write(int *a, int *b) {
        *a = 10;
        return *b;
    }
    

    其编译为

    read_write:
            mov     DWORD PTR [rdi], 10
            mov     eax, DWORD PTR [rsi]
            ret
    

    Godbolt

    很明显,如果 *a *b 是不相关的内存位置,则根据 Intel Manual Vol.3 :

    • store 可以稍后重新排序 load 到不相关的内存位置

    很明显,由于存储缓冲区转发,这种重新排序会在硬件级别上发生,但《标准》明确指出,“之前排序”是关于如何准确地进行评估:

    5.1.2.3/3 :

    之前排序是一种不对称的、传递的、成对的关系 在由单个线程执行的求值之间,这会引发 这些评价之间的偏序。给定任意两个评估A和 B、 如果A的顺序在B之前,则A的执行应在B之前 B的执行。(相反,如果A在B之前排序,则B为 在A之后测序。)如果A没有在B之前或之后测序,那么A 和B是未排序的。

    在示例中未指定。所以这意味着商店 *a = 10; 未排序的 到负载 b 。本标准在 5.1.2.3/6 那个

    对对象的易失性访问严格根据 抽象机器的规则。


    问题: 至少使一个指针指向是否足够 volatile 变量,或两者兼有 int *a int *b 必须是 不稳定的 以确保在实现之前进行排序?

    我的意思是:

    int read_write(volatile int *a, int *b) {
        *a = 10; 
        //sequenced before since a points to volatile int
        return *b;
    }
    

    int read_write(int *a, volatile int *b) {
        *a = 10; 
        //sequenced before since b points to volatile int
        return *b;
    }
    

    但是添加 不稳定的 不会改变编译的代码,这是相对令人困惑的。

    1 回复  |  直到 2 年前
        1
  •  5
  •   Eric Postpischil    2 年前

    在示例中未指定。

    不,在您的示例中没有未指定排序。C 2018 6.8 4说:

    A. 完整表达式 是一个表达式,它不是另一个表达式的一部分,也不是声明符或抽象声明符的一部分。完整表达式的求值和下一个要求值的完整表达式的赋值之间有一个序列点。

    在示例代码中:

        *a = 10;
        return *b;
    

    *a = 10 是一个不属于另一个表达式的表达式,因此它是一个完整的表达式。和 *b 也是一个不属于另一个表达式的表达式,因此它是一个完整的表达式。所以这两个表达式之间有一个序列点。

    5.1.2.3 3说:

    的存在 序列点 在表达式的求值之间 A. B 意味着与相关的每个值计算和副作用 A. 在每次值计算和与相关的副作用之前进行排序 B

    因此,的值计算和副作用 *a=10 在的值计算之前排序 b .

    根据《英特尔手册》第3卷:

    • store 可以稍后重新排序 load 到不相关的内存位置

    这与C语义无关。硬件可以以某种方式执行操作,但其最终结果将符合所需的C语义(因为编译器是为了生成这样做的指令而编写的,即使硬件重新排序了操作)。

    C语义没有定义硬件必须做什么,除了 可观察的行为 的。5.1.2.3.6表示可观察到的行为是:

    对易失性对象的访问严格按照抽象机器的规则进行评估。

    在程序终止时,写入文件的所有数据应与根据抽象语义执行程序所产生的结果相同。

    交互式设备的输入和输出动态应按照7.21.3的规定进行。这些要求的目的是尽快出现无缓冲或行缓冲的输出,以确保在程序等待输入之前实际出现提示消息。

    因此,只要硬件最终产生所需的输入和输出以及上述其他行为,它执行操作的顺序无关紧要。

    问题: 至少使一个指针指向是否足够 volatile 变量,或两者兼有 int *a int *b 必须是 不稳定的 以确保在实现之前进行排序?

    制作 *a b 或者两种volatile都不会改变已经存在的C语义中的排序。要使这两种语言都易失性,就需要在程序外部以与C语义相同的顺序对它们进行访问。使一个易失性而不是另一个不需要对它们的访问,正如在程序外部所看到的那样,以与C语义相同的顺序发生。

    对易失性对象的访问是由实现定义的。因此,C实现可能会将其定义为执行访问它的指令,也可能会将它定义为内存总线上出现的访问请求,也可能以其他方式定义它。