间接运算符的 C++ 标准描述是否保证内存写入不会被优化?

问题描述 投票:0回答:2

这基本上是这个问题的延续。到目前为止,如果我有这样的函数,看起来是这样的:

void SecureZeroMemory( void* ptr, size_t cnt )
{
   volatile char *vptr = (volatile char *)ptr;
   while (cnt) {
       *vptr = 0;
       vptr++;
       cnt--;
   }
}

并这样称呼它:

{
    char buffer[size];
    SecureZeroMemory( buffer, size );
}

然后,由于

buffer
未声明为 易失性,因此使用指向易失性的指针并不重要 - 数据本身不是易失性的,因此写入变量不构成可观察的行为(1.9/6)并且允许编译器优化它们。

但是最近我发现一个声明,只有指针声明才是重要的。具体来说,C++03 5.3.1/1 描述了间接 (*),如下所示:

一元 * 运算符执行间接寻址 [...] 如果表达式的类型是“指向 T 的指针”,则结果的类型是“T”。

因此,我们的主张是,由于在

volatile char*
上使用间接,我们得到
volatile char
并写入那些确实构成可观察行为的内容,并且实际数据如何声明不再重要。

C++03 5.3.1/1 对间接寻址的描述是否真的保证使用

volatile T*
指针覆盖内存(如上面的示例中所示)构成可观察的行为并且不允许被优化掉?

c++ optimization compiler-optimization indirection
2个回答
4
投票

我很确定“新”引用添加的所有内容是

*vptr
是类型为
volatile char
的左值表达式。

左值的类型不会影响该左值表达式所引用的对象的类型,这与指向非 const 对象的 const 指针不会以某种方式使该对象成为 const 的原因相同。所以最初的分析并没有受到这句话的影响——object仍然不具有易失性限定类型。

按照通常的说法,我们会说

*vptr
的类型是
volatile char &
,但是 5/5 说,“如果表达式最初具有“引用 T”类型,则在进行任何进一步分析之前,类型将调整为 T ”。这就是为什么
*vptr
被称为类型
volatile char
,而不是
volatile char &
的原因——在分析任何表达式之前,您需要从类型中删除引用,即使它是左值。

[编辑:我的答案曾经有一些关于简历资格对于整数类型的非对象值来说无关紧要的文本。这是真的(非类类型的左值到右值转换丢弃 cv 限定符,4.1/1)但无关紧要(我错误地认为,因为您引用的文本提到了非引用类型,所以它正在谈论此后的类型转换)]


4
投票

这是一个有趣的问题。 我认为其意图是 标准是这应该可行。 关于阅读标准 (C++03,§1.9/6,7)但是:

抽象机的可观察行为是它的序列 对易失性数据的读取和写入以及对库 I/O 的调用 功能。

访问由易失性左值指定的对象,修改 一个对象,调用库 I/O 函数,或者调用 执行任何这些操作的函数都是 side 效果,即执行状态的变化 环境。 表达式的求值可能会产生 side 影响。在执行序列中的某些指定点 称为序列点,之前的所有副作用 评估应完整且无副作用 应进行后续评估。

两段中的措辞差异似乎是 重要:“可观察的行为”是读取序列 并写入易失性数据。 在你的情况下,

buffer
不是 易失性数据,因此编译器大概可以自由优化 访问远离。 我不认为这是本意,但是 好像是这么说的。

就您而言,优化会特别简单, 因为到 volatile 的转换发生在函数本身中。 编译器可以很容易地确定

vptr
不指向 实际上是不稳定的数据。 如果改变参数 输入
void volatile*
,然后编译器必须看到 同时调用站点和函数,以便 安全地进行优化。

最后,无论标准如何规定,编译器 对

volatile
有自己的解释。 在实践中, 大多数(如果不是全部)编译器都会假设您正在使用
volatile
出于某种原因,并且会生成机器 进行写入的说明。 (实际上,这就是全部 他们会这样做,这意味着写入的顺序变成 在线程外部可见线程所在的代码 运行仍然未定义。 这对您的使用来说不是问题, 但它还有很多其他用途。)

© www.soinside.com 2019 - 2024. All rights reserved.