我将为as-if规则和volatile关键字添加详细的参考。(在这些页面的底部,遵循“另请参见”和“参考”以追溯到原始规格,但我发现cppreference.com更易于阅读/理解。)
特别是,我希望您阅读本节
volatile对象-类型为volatile限定的对象,或volatile对象的子对象,或const-volatile对象的可变子对象。出于优化目的(即在单个执行线程中,通过volatile限定类型的glvalue表达式进行的每次访问(读取或写入操作,成员函数调用等)都被视为可见的副作用)无法优化访问,也不会因其他易见的副作用(在volatile访问之前或之后)而被优化或重新排序,这使得volatile对象适合与信号处理程序进行通信,但不适合与其他执行线程进行通信,请参见std :: memory_order )。任何通过非易失性glvalue来引用易失性对象的尝试(例如,通过对非易失性类型的引用或指针)都会导致未定义的行为。
因此,volatile关键字专门用于在glvalues上禁用编译器优化。这里volatile关键字可能会影响的唯一一件事是return x
,编译器可以使用该函数的其余部分执行任何操作。
编译器可以优化返回值的多少取决于在这种情况下允许编译器优化x的访问量(因为它不会重新排序任何东西,严格来说,是不删除return表达式。 ,但是它是在对堆栈进行读写,这应该可以简化。)因此,在我阅读本文时,这是允许编译器优化多少的灰色区域,并且可以很容易地以两种方式争论。
旁注:在这些情况下,请始终假定编译器将执行与您想要/需要的相反的操作。您应该禁用优化(至少对于此模块而言),或者尝试为所需的内容找到更定义的行为。(这也是为什么单元测试如此重要的原因)如果您认为它是一个缺陷,则应与C ++开发人员联系。
这一切仍然很难阅读,因此请尝试包含我认为相关的内容,以便您自己阅读。
glvalue glvalue表达式是lvalue或xvalue。
特性:
通过左值到右值,数组到指针或函数到指针的隐式转换,可以将glvalue隐式转换为prvalue。glvalue可能是多态的:它标识的对象的动态类型不一定是表达式的静态类型。在表达式允许的情况下,glvalue的类型可以不完整。
xvalue以下表达式是xvalue表达式:
函数调用或重载的运算符表达式,其返回类型是对对象的右值引用,例如std :: move(x); a [n],内置的下标表达式,其中一个操作数是一个数组rvalue;am,对象表达式的成员,其中a是一个右值,m是非引用类型的非静态数据成员;a。* mp,对象表达式成员的指针,其中a是右值,mp是数据成员的指针;一种 ?b:c,某些b和c的三元条件表达式(有关详细信息,请参见定义);一个对对象类型进行右值引用的强制转换表达式,例如static_cast(x); 在临时实现之后,指定临时对象的任何表达式。(自C ++ 17起)属性:
与右值相同(如下)。与glvalue相同(如下)。特别是,像所有右值一样,x值绑定到右值引用,并且像所有glvalue一样,x值可以是多态的,非类xvalue可以是cv限定的。
左值以下表达式是左值表达式:
变量,函数或数据成员的名称,无论类型如何,例如std :: cin或std :: endl。即使变量的类型是右值引用,包含其名称的表达式也是左值表达式;函数调用或重载的运算符表达式,其返回类型为左值引用,例如std :: getline(std :: cin,str),std :: cout <<,str1 = str2或++ it;a = b,a + = b,a%= b以及所有其他内置赋值和复合赋值表达式;++ a和--a,内置的预递增和递减表达式;* p,内置的间接表达式;a [n]和p [n]是内置的下标表达式,除非a是数组右值(自C ++ 11起);am,对象表达式的成员,除非m是成员枚举数或非静态成员函数,或其中a是右值,m是非引用类型的非静态数据成员;或者 p-> m,指针表达式的内置成员,除非m是成员枚举数或非静态成员函数;a。* mp,对象表达式成员的指针,其中a是左值,mp是数据成员的指针;p-> * mp,指向指针表达式成员的内置指针,其中mp是指向数据成员的指针;a,b是内置的逗号表达式,其中b是左值;一种 ?b:c,某些b和c的三元条件表达式(例如,当它们都是相同类型的左值时,请参见定义);字符串文字,例如“ Hello,world!”;转换为左值引用类型的表达式,例如static_cast(x); 函数调用或重载的运算符表达式,其返回类型是对函数的右值引用;将值转换为对函数类型的引用的强制转换表达式,例如static_cast(x)。(自C ++ 11起)属性:
与glvalue相同(如下)。可以使用左值的地址:&++ i 1
和&std :: endl是有效的表达式。可以将可修改的左值用作内置赋值和复合赋值运算符的左侧操作数。左值可用于初始化左值引用;这会将新名称与表达式标识的对象相关联。
假设规则
只要满足以下条件,就允许C ++编译器对程序进行任何更改:
1)在每个序列点,所有易失性对象的值都是稳定的(以前的评估已完成,新评估未开始)(直到C ++ 11)1)严格根据语义对易失性对象进行访问(读取和写入)它们出现在其中的表达式。特别是,它们不会相对于同一线程上的其他易失性访问而重新排序。(自C ++ 11起)2)在程序终止时,写入文件的数据与程序按写入的方式执行完全相同。3)在程序等待输入之前,将显示发送到交互式设备的提示文本。4)如果支持ISO C编译指示#pragma STDC FENV_ACCESS并将其设置为ON,
如果您想阅读规格,我相信这些是您需要阅读的
参考文献
C11标准(ISO / IEC 9899:2011):6.7.3类型限定符(p:121-123)
C99标准(ISO / IEC 9899:1999):6.7.3类型限定符(p:108-110)
C89 / C90标准(ISO / IEC 9899:1990):3.5.3类型限定符