通过易失性引用/指针访问声明的非易失性对象是否赋予所述访问易失性规则? [英] Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?

查看:37
本文介绍了通过易失性引用/指针访问声明的非易失性对象是否赋予所述访问易失性规则?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这将是一篇很长的文章,为了将其语境化并提供尽可能多的信息,我必须浏览各种链接和引号 - 这通常是我们进入 C/C++ 标准兔子洞的唯一方法.如果您对这篇文章有更好的引用或任何其他改进,请告诉我.但先总结一下,你可以责怪@zwol 我发这个;-) 目的是从两个命题中找出真相:

  • C 和(通过导入;见注释)C++ 标准要求通过 volatile *volatile & 访问必须引用最初声明的对象volatile 为了有 volatile 语义?
  • 或者 正在通过 volatile 指针/引用访问一个非 volatile 限定的对象易变?

无论哪种方式,如果(看起来)措辞与意图相比有些含糊不清 - 我们能否在标准本身中明确说明?

这些相互排斥的解释中的第一种更为普遍,这并非完全没有根据.但是,我希望表明有大量合理怀疑"支持第二条 - 特别是当我们回到基本原理和工作组论文中的一些先前段落时.


公认的智慧:引用的对象本身必须声明为volatile

昨天的热门问题 易失性"的定义是否如此易变,或者 GCC 是否存在一些标准合规性问题? 是通过假设产生的volatile 引用将赋予非 volatile 引用对象 volatile 行为 - 但发现它没有,或者在不同程度上和在不可预测的方式.

接受的答案最初得出结论,只有所指对象的声明类型才重要.这个和大多数评论似乎都同意等效原则正在发挥作用,正如我们对 const 所熟知的:如果引用具有与引用对象相同的 cv 限定条件:

<块引用>

那一段的关键词是对象.volatile sig_atomic_t flag; 是一个 volatile 对象.*(volatile char *)foo 只是通过 volatile 限定的左值访问,标准不要求它有任何特殊效果.– zwol

这种解释似乎被广泛接受,如对这个类似但希望不是重复的问题的回答所示:对指向非易失性对象的 volatile 指针行为的要求 但即使在那里也存在不确定性:在回答说不",然后说可能"!无论如何...让我们检查标准,看看'不'是基于什么.


标准说了什么......或没有

C11、N1548、§6.7.3:虽然很明显,访问volatile 定义的对象是 UBconst 类型通过不共享所述限定符的指针...

<块引用>

6 如果试图通过使用具有非const 的左值来修改用const 限定类型定义的对象- 限定类型,行为未定义.如果尝试通过使用具有非volatile限定类型的左值来引用volatile限定类型定义的对象,行为未定义.(133)

...标准似乎没有明确提到相反的情况,即volatile.此外,在总结 volatile 及其上的操作时,它现在谈论的是 具有 volatile 限定类型的对象:

<块引用>

7 具有 volatile 限定类型的对象可能会以实现未知的方式进行修改或具有其他未知的副作用.因此,任何引用此类对象的表达式都应严格按照抽象机的规则进行评估,如 5.1.2.3 中所述.此外,在每个序列点,最后存储在对象中的值应与抽象机规定的值一致,除非被前面提到的未知因素修改. (134) 什么构成对具有 volatile<的对象的访问/code>-qualified 类型是实现定义的.

我们是否假设has"等同于被定义为"?或 可以有" 是指对象限定符和引用限定符的组合吗?

一位评论者用这种措辞很好地总结了这个问题:

<块引用>

n1548 §6.7.3 ¶6 开始,标准使用短语用 volatile 限定类型定义的对象"来区分它与具有 volatile 限定类型的左值".不幸的是,这种对象定义为"与左值"的区别没有继续下去,然后标准使用具有 volatile 限定类型的对象",并说什么构成对具有 volatile 限定类型的对象的访问是实现定义的"(为了清楚起见,可以说左值"或对象定义为").那好吧.– Dietrich Epp

同一部分的第 4 段似乎不太经常被引用,但很可能是相关的,我们将在下一部分中看到.


合理怀疑:是否是一个 volatile 指针/引用,旨在为其取消引用赋予 volatile 语义?

上述答案有一个评论,其中作者引用了委员会早先的声明,该声明对引用必须与所指对象相匹配"的想法表示怀疑:

<块引用>

有趣的是,[C99 volatile 的基本原理] 中有一句话暗示委员会意为*(volatile T*)x 强制对 x 的访问被视为 volatile;但标准的实际措辞并没有达到这一点.– zwol

我们可以从前面提到的第二个线程中找到关于这一部分基本原理的更多信息:指向非易失性对象的指针的行为要求

<块引用>

另一方面,这篇文章引自 6.7.国际标准的基本原理 3--编程语言--C:

<块引用>

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

philipxy

那个字节线程,我们被称为 C99s6.7.3 p3 - 又名 C11 的 p4 - 以及这个分析:

<块引用>

有问题的段落就在理由文件中的第 6.7.3.1 节之前.如果您还需要引用标准文档本身,请引用 6.7.3 p3:

与限定类型关联的属性仅对左值表达式有意义.

表达式(volatile WHATEVER) non_volatile_object_identifier不是左值,因此volatile"限定符没有意义.

反之,表达式* (volatile WHATEVER *) &non_volatile_object_identifier一个左值(它可以放在赋值语句的左侧),所以volatile"限定符的属性在这种情况下具有其预期的含义.

Tim Rentsch

WG 论文 N1381.这引入了附件 memset_s() 来做 OP 想要的 - 保证内存的非省略填充.在讨论可能的实现时,它似乎支持这个想法 - 通过省略说明任何要求 - 使用 volatile 指针来改变非 volatile 对象应该 根据指针的限定符生成代码,而不管引用对象的...

<块引用>

  1. 独立于平台的secure-memset"解决方案:

void *secure_memset(void *v, int c , size_t n) {易失性无符号字符 *p = v;而 (n--) *p++ = c;返回 v;}

<块引用>

这种方法将防止内存清理被优化掉,并且它应该适用于任何符合标准的平台.

...并且注意编译器不这样做...

<块引用>

最近注意到一些编译器不总是遵守 volatile 限定符而违反了标准.


谁是对的?

那太累了.这里肯定有很大的解释空间,这取决于您碰巧读过哪些文件,哪些没有读过,以及您如何选择解释许多不够具体的单词.似乎很明显有些不对劲:要么:

  • 理由和 N1381 措辞错误或随意,或
  • 它们被明确地追溯无效...或
  • 该标准措辞错误或随意.

我希望我们能做得比过去似乎围绕这一点的所有含糊不清和猜测做得更好 - 并得到一个更确定的声明.为此,非常欢迎专家提供任何进一步的来源和想法.

解决方案

通过易失性引用/指针访问声明的非易失性对象是否赋予所述访问易失性规则?

volatile 在 C & 中并不意味着同样的事情.C++.C++ 标准使通过 volatile 左值的访问变得可观察.[1] 它说它打算与 C 行为相同.这就是 C 基本原理中描述的行为.尽管如此,C 标准说对 volatile 声明的对象的访问是可观察的.(请注意,通过非 volatile 左值访问 volatile 声明的对象是未定义的.)

然而. 有一份缺陷报告基本上已经获得委员会的同意(尽管仍然开放),标准应该说,并且意图一直是,并且实现总是反映,它重要的不是对象的波动性(根据标准),而是访问(的左值)的波动性(根据基本原理).

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

当然,关于可观察行为的处理是依赖于实现的.

真的没有任何歧义.只是人们不能相信 C 标准行为可能是它的样子,因为那不是 volatile 之前的历史用法(当地址文字左值是被视为易失性对象),如基本原理所预期的那样,由编译器在之前和之后实现,由 C++ 标准解释和描述,在 DR 中更正.同样,标准很清楚,因为它没有说非易失性访问是可观察的,所以它们不是.(副作用"是用于定义评估偏序的术语.)

[1] 或者至少希望现在是这样.来自 underscore_d 的评论:

<块引用>

对于 C++,另见 P0612R0:NB 评论 CH2:易变的,本月通过了 清理 C++ 标准中关于易失性对象"的一些遗留问题,当真正通过易失性泛左值访问时,它的意思是什么(大概/希望是 C 的意思).

This'll be a long one, as to contextualise it and provide as much info as I can, I must meander through various links and quotes - as is often the only way once we enter the C/C++ Standard Rabbit Hole. If you have better citations or any other improvements to this post, please let me know. But to summarise up front, you can blame @zwol for me posting this ;-) and the aim is to find the truth from among two propositions:

  • Do the C and (by import; see comments) C++ Standards require that accesses via volatile * or volatile & must refer to an object originally declared volatile in order to have volatile semantics?
  • Or is accessing a non-volatile-qualified object through a volatile pointer/reference sufficient/supposed to make said accesses behave as if the object was declared volatile?

And either way, if (as it seems) the wording is somewhat ambiguous compared to the intent - can we get that made clear in the Standards themselves?

The 1st of these mutually exclusive interpretations is more commonly held, and that's not entirely without basis. However, I hope to show that there's a significant amount of "reasonable doubt" in favour of the 2nd - especially when we go back to some prior passages in the Rationale and WG Papers.


Accepted wisdom: the referred object itself must have been declared volatile

Yesterday's popular question Is the definition of "volatile" this volatile, or is GCC having some standard compliancy problems? arose by assuming a volatile reference would confer volatile behaviour on a non-volatile referent - but finding that it did not, or did to varying degrees and in an unpredictable way.

The accepted answer initially concluded that only the declared type of the referent mattered. This and most comments seemed to agree that equivalent principles are in play as we know well for const: the behaviour would only be volatile (or defined at all) if the reference has the same cv-qualification as the referred object:

The key word in that passage is object. volatile sig_atomic_t flag; is a volatile object. *(volatile char *)foo is merely an access through a volatile-qualified lvalue and the standard does not require that to have any special effects. – zwol

This interpretation appears to be quite widely held, as seen in the responses to this similar-but-hopefully-not-duplicate question: Requirements for behavior of pointer-to-volatile pointing to non-volatile object But there's uncertainty even there: right after the answer says 'no', it then says 'maybe'! Anyway...let's check the Standard to see what the 'no's are based on.


What the Standard says... or doesn't

C11, N1548, §6.7.3: Whereas it's clear that it's UB to access an object defined with volatile or const type via a pointer that doesn't share said qualifier...

6 If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined. If an attempt is made to refer to an object defined with a volatile-qualified type through use of an lvalue with non-volatile-qualified type, the behavior is undefined.(133)

...the Standard doesn't seem to explicitly mention the opposite scenario, namely for volatile. Moreover, when summarising volatile and operations thereon, it now talks about an object that has volatile-qualified type:

7 An object that has volatile-qualified type may be modified in ways unknown to the implementation or have other unknown side effects. Therefore any expression referring to such an object shall be evaluated strictly according to the rules of the abstract machine, as described in 5.1.2.3. Furthermore, at every sequence point the value last stored in the object shall agree with that prescribed by the abstract machine, except as modified by the unknown factors mentioned previously.(134) What constitutes an access to an object that has volatile-qualified type is implementation-defined.

Are we to assume "has" is equivalent to "was defined with"? or can "has" refer to a combination of object and reference qualifiers?

A commenter summed up the issue with this sort of wording well:

From n1548 §6.7.3 ¶6 the standard uses the phrase "object defined with a volatile-qualified type" to distinguish it from "lvalue with volatile-qualified type". It's unfortunate that this "object defined with" versus "lvalue" distinction does not carry forward, and the standard then uses "object that has volatile-qualified type", and says that "what constitutes access to an object that has volatile-qualified type is implementation-defined" (which could have said "lvalue" or "object defined with" for clarity). Oh well. – Dietrich Epp

Paragraph 4 of the same section seems to be less frequently quoted but might well be relevant, as we'll see in the next section.


Reasonable doubt: Is/Was a volatile pointer/reference intended to confer volatile semantics on its dereference?

The aforementioned answer has a comment wherein the author cites an earlier statement by the Committee that cast doubt on the 'reference must match referent' idea:

Interestingly, there is one sentence in there [C99 Rationale for volatile] that implies that the committee meant for *(volatile T*)x to force that one access to x to be treated as volatile; but the actual wording of the standard does not achieve this. – zwol

We can find a bit more info on this bit of the Rationale, from the 2nd aforementioned thread: Requirements for behavior of pointer-to-volatile pointing to non-volatile object

On the other hand, this post quotes from the 6.7.3 of the Rationale for International Standard--Programming Languages--C:

A cast of a value to a qualified type has no effect; the qualification (volatile, say) can have no effect on the access since it has occurred prior to the case. If it is necessary to access a non-volatile object using volatile semantics, the technique is to cast the address of the object to the appropriate pointer-to-qualified type, then dereference that pointer.

philipxy

And from that Bytes thread, we're referred to C99 s6.7.3 p3 - a.k.a. C11's p4 - and this analysis:

The paragraph in question is just before section 6.7.3.1 in the rationale document. If you also need to quote from the standard document itself, cite 6.7.3 p3:

The properties associated with qualified types are meaningful only for expressions that are lvalues.

The expression (volatile WHATEVER) non_volatile_object_identifier is not an lvalue, hence the 'volatile' qualifier is meaningless.

Conversely, the expression * (volatile WHATEVER *) & non_volatile_object_identifier is an lvalue (it may be placed on the left side of an assignment statement), so the property of the 'volatile' qualifier has its intended meaning in this case.

Tim Rentsch

There is a very specific demonstration supporting this idea, with specific regard to the 1st linked question, in WG Paper N1381. This introduced the Annexed memset_s() to do what that OP wanted - guarantee non-elided filling of memory. In discussing possible implementations, it seems to support the idea - by omitting to state any requirement - that using a volatile pointer to alter a non-volatile object should generate code based on the qualifier of the pointer, regardless of that of the referred object...

  1. Platform-independent ' secure-memset' solution:

void *secure_memset(void *v, int c , size_t n) {
    volatile unsigned char *p = v;
    while (n--) *p++ = c;
    return v;
}

This approach will prevent the clearing of memory from being optimized away, and it should work on any standard-compliant platform.

...and that compilers not doing this are on notice...

There has been recent notice that some compilers violate the standard by not always respecting the volatile qualifier.


Who's right?

That was exhausting. There's certainly a lot of room for interpretation here, depending on which documents you happen to have read vs which not, and how you choose to interpret a lot of words that aren't specific enough. It seems clear that something is amiss: either:

  • the Rationale and N1381 are wrongly or haphazardly worded, or
  • they were specifically invalidated retroactively... or
  • the Standard is wrongly or haphazardly worded.

I'd hope that we can do better than all the ambiguity and speculation that seems to have surrounded this in the past - and get a more conclusive statement put on record. To that end, any further sources and thoughts from experts would be very welcome.

解决方案

Does accessing a declared non-volatile object through a volatile reference/pointer confer volatile rules upon said accesses?

volatile doesn't mean the same thing in C & C++. The C++ standard makes accesses through volatile lvalues observable. [1] It says that it intends this to be the same as C behaviour. And that is the behaviour described in the C Rationale. Nevertheless the C Standard says that accesses to a volatile-declared objects are observable. (Note that accessing a volatile-declared object via a non-volatile lvalue is undefined.)

However. There is a defect report which essentially has committee agreement (although still open) that the Standard should say, and that the intent has always been, and that implementations have always reflected, that it is not the volatility of an object that matters (per the Standard) but of the volatility of (the lvalue of) an access (per the Rationale).

Defect Report Summary for C11 Version 1.10 Date: April 2016 DR 476 volatile semantics for lvalues 04/2016 Open

Of course, what is done about observable behaviour is implementation-dependent.

There really isn't any ambiguity. It's just that people can't believe that the C Standard behaviour could be what it is, because that is not the historical usage pre-volatile (when address-literal lvalues were taken to be of volatile objects), as intended by the Rationale, as implemented by compilers before and since, as interpreted and described by the C++ Standard, as corrected in the DR. Similarly, the standard is clear in that it doesn't say that non-volatile accesses are observable, so they're not. (And "side effect" is a term used in defining the evaluation partial order.)

[1] Or at least hopefully it does now. From a comment from underscore_d:

For C++, see also P0612R0: NB comment CH 2: volatile, which was adopted this month to clean up some leftover talk about "volatile objects" in the C++ Standard, when really accesses through volatile glvalues are what it meant (as, presumably/hopefully, what C meant).

这篇关于通过易失性引用/指针访问声明的非易失性对象是否赋予所述访问易失性规则?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆