我正在阅读C标准中与多线程执行相关的一部分,并且对用于定义inter-thread happens beforeSequenced-before的定义非常困惑:

5.1.2.4/16:

Ainter-thread happens before B,如果对于某个动作X

AX之前,X线程间在B之前


最初我认为,如果在程序顺序中动作A在动作B之前,那么AB之前排序,但请考虑下面这个简单的例子:

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:

Sequenced before是一个不对称的、传递的、成对的关系 在由单个线程执行的计算之间,这会导致 这些评价中的部分顺序.给定任意两个评价A和 B,如果A在B之前排序,则A的执行应先于 B的执行.(相反,如果A在B之前排序,则B是 A后排序).如果A不是在B之前或之后排序,则A 而B是未排序的.

在本例中未指定.因此,这意味着存储*a = 10;对于加载*bunsequenced.《旗帜报》在5.1.2.3/6处指出

对对象的易失性访问严格按照 抽象机器的规则.


104使至少一个指针指向volatile变量是否足够,或者int *aint *b必须都是volatile以确保先排序后运算?

我的意思是:

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

但是加volatile并不会改变编译后的代码,这是比较令人困惑的.

推荐答案

在本例中未指定.

不,在您的示例中并不是没有指定顺序.C 2018 6.8 4表示:

是既不是另一个表达式的一部分,也不是声明符或抽象声明符…的一部分的表达式在一个完整表达式的求值和下一个要求值的完整表达式的求值之间存在一个序列点.

在示例代码中:

    *a = 10;
    return *b;

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

5.1.2.3 3表示:

…在表达式AB的求值之间存在sequence point意味着与A相关联的每个值计算和副作用在与B…相关联的每个值计算和副作用之前被排序

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

…根据英特尔手册第3卷:

  • store可以与稍后的load重新排序到无关的存储器位置

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

除了程序的observable behavior之外,C语言的语义并没有定义硬件必须做什么.5.1.2.3 6表示可观察到的行为是:

-对易失性对象的访问严格按照抽象机的规则进行判断.

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

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

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

104使至少一个指针指向volatile变量是否足够,或者int *aint *b必须都是volatile以确保先排序后运算?

*a和/或*b设置为易失性不会改变C语义中的顺序,C语义已经存在.使两者都成为易失性将要求对它们的访问以与C语义相同的顺序发生,就像在程序之外看到的那样.将一个设置为易失性,而不是另一个设置为易失性,将不需要以与C语义相同的顺序访问它们,就像在程序之外看到的那样.

对volatile对象的访问是由实现定义的.因此,C实现可能会将其定义为执行访问它的指令,或者它可能会将其定义为出现在内存总线上的访问请求,或者它可能以其他方式定义它.

C++相关问答推荐

海湾合作委员会是否保证大小匹配的访问?

为什么在C中二维字符数组会有这样的行为?

警告:C++中数组下标的类型为‘char’[-Wchar-subpts]

如何在C中通过套接字自定义数据类型读取原始变量?

致命:ThreadSaniizer:在Linux内核6.6+上运行时意外的内存映射

函数的限制限定指针参数允许优化调用方函数吗?

为什么此共享库没有预期的依赖项?

如何在C中只对字符串(包含数字、单词等)中的数字进行重复操作?

为什么我从CSV文件中进行排序和搜索的代码没有显示数据的所有结果?

从C中的函数返回静态字符串是不是一种糟糕的做法?

CS50 pset 5的皱眉脸正确地处理了大多数基本单词,并且拼写判断不区分大小写.

与外部SPI闪存通信时是否应禁用中断?

C中的数组下标和指针算法给出了不同的结果

使用mmap为N整数分配内存

按字典顺序打印具有给定字符的所有可能字符串

在C中使用字符串时是否不需要内存分配?

分支预测和UB(未定义的行为)

如何不断地用C读取文件?

std::malloc/calloc/realloc/free 与纯 C 的 malloc/calloc/realloc/free 有什么不同

C 中类型说明符的顺序重要吗?