C标准Sequenced-before关系疑问:volatile指针需单个还是两个?
我正在研读C标准的多线程执行章节,对用于定义inter-thread happens before的Sequenced-before定义感到困惑。
5.1.2.4/16:
若存在某个操作X,则A inter-thread happens before B,当且仅当:
A is sequenced before X 且 X inter-thread happens before B
我原本以为,只要操作A在程序顺序中排在B之前,A就sequenced before 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
根据Intel手册卷3的说明,如果*a和*b指向无关内存地址,硬件层面会把存储操作和后续针对无关地址的加载操作重排序——这是通过存储缓冲区转发实现的。但C标准明确指出Sequenced-before关乎求值的具体执行规则:
5.1.2.3/3:
Sequenced before是单个线程内执行的求值之间的非对称、可传递的两两关系,在这些求值间引入偏序。对于任意两个求值A和B,若A is sequenced before B,则A的执行必须先于B的执行。(反之,若A is sequenced before B,则B is sequenced after A。)若A既不sequenced before也不sequenced after B,则A和B是unsequenced。
在上面的例子里,这个关系没有明确指定,意味着*a = 10;的存储操作和*b的加载操作是unsequenced的。而C标准5.1.2.3/6提到:
对volatile对象的访问严格按照抽象机的规则求值。
核心问题
要确保这两个操作之间存在Sequenced-before关系,是只需要至少一个指针指向volatile变量,还是必须让int *a和int *b都指向volatile变量?
比如下面这两个示例:
int read_write(volatile int *a, int *b) { *a = 10; // 这里是否因为a指向volatile int,就存在sequenced before关系? return *b; }
以及:
int read_write(int *a, volatile int *b) { *a = 10; // 这里是否因为b指向volatile int,就存在sequenced before关系? return *b; }
但实际测试中,添加volatile并没有改变编译生成的汇编代码,这让我很困惑。
内容的提问来源于stack exchange,提问作者St.Antario

