问题 指针指向非易失性对象的指针行为的要求


C11 6.7.3类型限定词,第7段,内容如下:

具有volatile限定类型的对象可能以实现未知的方式进行修改,或者具有其他未知的副作用。因此,任何涉及这种对象的表达都应严格按照抽象机的规则进行评估,如5.1.2.3所述。

在以下示例中,第三行中访问的对象是否受上述规则约束?

int x;
volatile int *p = &x;
*p = 42;

换句话说,左值这个事实 *p 有类型 volatile int 表示正在访问易失性对象,或者是否正在访问它 p 指向非易失性对象 x 意味着编译器可以利用这些知识进行优化并省略易失性访问?

由于它可能是有意义的,我感兴趣的特定用例不在普通C的范围内;它涉及使用pre-C11结构(可以是内联asm或简称为黑盒子)进行线程同步的原子,用于原子比较和交换,具有以下习语:

do {
    tmp = *p;
    new = f(tmp);
} while (atomic_cas(p, tmp, new) != success);

这里是指针 p 会有类型 volatile int *,但是我担心当实际指向的对象是非易失性时会发生什么,特别是编译器是否可以将单个访问转换为 *p 从 tmp = *p 进入以下形式的两次访问:

do {
    new = f(*p);
} while (atomic_cas(p, *p, new) != success);

这显然会导致代码不正确。因此,目标是确定是否所有这些指向的对象实际上都需要 volatile int


3983
2018-02-22 04:06


起源

符合程序将无法检测到差异,如访问和修改 x 不是可观察到的副作用。因此,编译器可以在as-if规则下进行优化。 - Igor Tandetnik
@IgorTandetnik:这也是我倾向于解释它的方式,但它似乎不是一个流行的理论,我仍然怀疑它是否正确。如果 x 永远不会被访问,除了 *(volatile int *)&x,和 &x 对于访问它的硬件是可见的,或者是否可见 sig_atomic_t 是 int 和 *(volatile int *)&x 从信号处理程序访问,那么是否有合法的可观察副作用? - R..
如果硬件访问x并且您声明它是非易失性的,那么您的硬件配置不会实现C虚拟机。 - philipxy
@supercat我认为我不明白你想要的观点;这个问题 定义 非挥发性的 int x 但是你在谈论一个易失定义的对象。为一个 宣言  extern int x 标准说“如果通过使用具有非volatile限定类型的左值来尝试引用使用volatile限定类型定义的对象,则行为是未定义的。”该标准不支持您的兼职挥发性。 - philipxy
@supercat PS我可以想象一个编译器和链接器对,它支持通过单个兼容时间易失性对象实现volatile定义的x源文件和非易失性定义的x soruce文件。但是这样的编译器加链接器不支持该定义“a”c程序的源文件对的标准语义。 - philipxy


答案:


更新 2017年2月18日

下面的答案引用并讨论了标准中的语言,基本原理中的一些矛盾语言和gnu.cc中的一些评论是矛盾。有一个缺陷报告基本上有标准应该说的委员会协议(虽然仍然是公开的),并且意图一直是,并且实施总是反映出,它不是波动性 一个东西 重要的(根据标准),但是波动性 (访问的左值) (根据理由)。 (归功于 奥拉夫 提到这个DR。)

C11版本1.10日期:2016年4月的缺陷报告摘要 DR 476左值的易失性语义 04/2016开放


不。因为访问的对象不易变。

目的 p 是指向volatile int的类型指针。但 x 不是volatile限定类型的对象。 p上的资格会影响通过它进行的访问,但不会影响它指向的对象的类型。通过volatile左值访问非限定类型对象没有限制。因此,通过p访问x不是对volatile属性类型的对象的访问。

(有关访问限定类型对象的限制,请参见6.7.3类型限定符。它只是说您无法通过不合格的左值访问volatile限定对象。)

另一方面, 这个帖子 引用国际标准理论的6.7.3 - 编程语言 - C:

将值转换为限定类型无效;资格   (volatile,比如说)因为它已经发生而对访问没有任何影响   案件之前。如果有必要访问非易失性对象   使用volatile语义,技术是转换地址   对象指向适当的指向限定类型​​,然后取消引用   那个指针。

但是,我无法在标准中找到语言,即语义基于左值类型。从 gnu.org

混淆的一个方面是用于定义的对象之间的区别   volatile类型和volatile左值。从C标准来看   在视图中,使用volatile类型定义的对象具有外部可见性   行为。您可以将这些对象视为只有很少的示波器   探针附加到它们,以便用户可以观察一些属性   访问它们,就像用户可以观察写入的数据一样   输出文件。但是,该标准并未明确是否   用户可以观察易失性左值对普通对象的访问。

[...]从标准中不清楚是否挥发性左值   提供更多的保证,一般比非易失性左值,如果   基础对象是普通的。

不,因为没有副作用:

即使是语义 *p 必须是挥发性的,但标准仍然说:

5.1.2.3程序执行4在抽象机器中,所有表达式都是   按语义指定的方式进行评估。一个实际的实现   如果它可以推断出它的表达式,则无需评估表达式的一部分   不使用值,不产生所需的副作用   (包括通过调用函数或访问volatile而导致的任何问题   目的)。

同样,代码中没有volatile对象。虽然是一个只能看到的编译单元 p 无法进行优化。

还要记住

6.7.3类型限定符7 [...]对具有volatile限定类型的对象的访问构成是实现定义的。

5.1.2.3程序执行8每个实现都可以定义抽象语义和实际语义之间更严格的对应关系。

因此,仅仅出现挥发性左值不会告诉你有什么“访问”。你没有权利谈论“单一访问权限 *p 从 tmp = *p“除了记录的实施行为。


7
2018-02-22 06:46



基本原理的引用似乎表明了与规范性文本不同的意图...... :-( - R..
即使一个对象是“普通的”,我也建议有必要允许中断,其他线程等可能需要访问它 volatile 语义,受限于非易失性访问可能以意想不到的方式运行。存在这样的现实情况:代码需要在运行时分配缓冲区并使用volatile语义访问它们。如果从中返回缓冲区 malloc 被认为是 volatile,通过非易失性指针访问会调用UB,所以这样的缓冲区必须是“普通的”。但如果......的平常性 - supercat
...缓冲区意味着编译器可以忽略 volatile 指向其内容的指针的限定符,在上下文之间共享数据所必需的语义是无法实现的。 - supercat
@supercat没有这种可能性。如果它以共享方式运行,那么您需要将其声明为volatile。这就是你如何告诉编译器它的行为方式。这是语言定义,否则编译器可以假设它不是共享/易失性。您正在向后使用UB(未定义的行为)。当标准说它未定义时,它是未定义的,程序可以做任何事情;您只是在描述一个程序,其硬件的行为方式与语言应该表现的方式不同。 - philipxy
@philipxy:通过什么机制可以为需要的进程动态分配内存 volatile 语义,如果没有使用 malloc? - supercat


不完全确定,但我认为重点是对象类型的区别 具有 和对象的类型 定义 用。

从C11(n1570)6.3.2.1 p1(脚注省略,emph.mine):

一个 左值 是一个表达式(对象类型不是 void)可能指定一个对象;如果左值在评估时未指定对象,则行为未定义。 当一个对象被称为具有特定类型时,该类型由用于指定该对象的左值指定。 [...]

它是左值,它定义了对象对特定访问的类型。相反, *p 不表示对象 定义 易挥发。例如,同上。 6.7.3 p6(emph.mine)读

[...]如果试图引用某个对象 定义 通过使用具有非volatile限定类型的左值,使用volatile限定类型,行为是未定义的。133)

133) 这适用于那些行为就像使用限定类型定义的对象,即使它们实际上从未被定义为程序中的对象(例如内存映射输入/输出地址处的对象)。

如果意图是允许显示的代码被优化,问题中的引用可能会读取 一个对象 具有 [定义为]波动限定类型可能会被修改[...]

标识符的“定义”*) 在6.7第5段中定义。

同上。 6.7.3 p7(什么构成对具有volatile限定类型的对象的访问是实现定义的。给实施者一些余地,但对我来说,意图似乎是修改由表示的对象的副作用 n 应该被认为是符合要求的实施可观察到的。

*) Afaik标准没有在任何地方定义“用(某种类型)定义的对象”所以我把它读作“一个对象,由一个定义的(某种类型)声明的标识符指定”。


2
2018-02-22 12:53



我倾向于在你对“有”这个词的分析的基础上接受这种解释。可比较的语言 const 出现在“可修改的左值”的定义中,其中“不可改变” 有 一个const限定类型“。在这种情况下,如果”have“指的是在其定义中声明对象的类型而不是正在使用的表达式的类型, *(const int *)&x 将是一个可修改的左值,这显然不是意图。 “有一个对象”和“一个表达式......可能指定一个没有对象的对象”之间存在一些区别 - R..
但对我来说,我最初引用的文本中的“对象”是由左值指定的对象,具有左值表达式的类型,这似乎仍然是合理的。 - R..
我会对“可比语言”分析保持警惕;许多人通过许多编辑制作了标准,他们似乎不太可能努力为这两个地方同步语言 - M.M