我将为
as-if规则和
volatile关键字添加详细参考。 (在这些页面底部,按照“另请参阅”和“参考文献”跟踪到原始规范,但我发现cppreference.com更易于阅读/理解。)
特别是,我希望你阅读这个部分。
易失性对象 - 其类型为易失性修饰符的对象,或易失性对象的子对象,或常易失性对象的可变子对象。通过易失性修饰符类型的glvalue表达式进行的每个访问(读取或写入操作,成员函数调用等)都被视为优化的可见副作用(也就是说,在单个执行线程内,易失性访问不能被优化掉或与先序或后续的另一个可见副作用重新排序。这使得易失性对象适合与信号处理程序通信,但不适合与另一个执行线程通信,请参见std::memory_order)。任何试图通过非易失性glvalue引用或指向非易失性类型的指针来引用易失性对象的尝试都会导致未定义的行为。
因此,易失性关键字专门用于禁用glvalues上的编译器优化。在这里,易失性关键字可能会影响的仅仅是return x
,编译器可以随意处理函数的其余部分。
编译器能够优化返回的程度取决于编译器在这种情况下被允许优化x的访问程度(因为它没有重新排序任何内容,严格来说,也没有删除返回表达式。有访问,但是它正在读写堆栈,应该能够简化。)因此,我认为这是一个灰色地带,编译器被允许优化的程度可以很容易地争论双方。
顺便提一下:在这些情况下,总是假设编译器会做与你想要/需要相反的事情。您应该禁用优化(至少对于此模块),或尝试找到更明确定义的行为。 (这也是单元测试非常重要的原因)。如果您认为这是缺陷,则应向C ++开发人员提出。
这些内容仍然很难理解,因此我尝试包含我认为相关的内容,以便您可以自己阅读。
glvalue glvalue表达式是lvalue或xvalue之一。
属性:
glvalue可以通过lvalue-to-rvalue、array-to-pointer或function-to-pointer隐式转换为prvalue。glvalue可以是多态的:它所标识的对象的动态类型不一定是表达式的静态类型。glvalue可以具有不完整的类型,在表达式允许的情况下。
以下表达式为xvalue表达式:
函数调用或重载运算符表达式,其返回类型是对象的右值引用,例如std::move(x);
a[n],内置下标表达式,其中一个操作数是数组右值;
a.m,对象成员表达式,其中a是右值,m是非引用类型的非静态数据成员;
a.*mp,对象指针成员表达式,其中a是右值,mp是指向数据成员的指针;
a ? b : c,三元条件表达式,对于某些b和c(详见定义);
转换表达式为对象类型的右值引用,例如static_cast(x);
任何指定临时对象的表达式,在临时材料化后。(自C++17起)
属性:
与rvalue相同;
与glvalue相同;
特别地,像所有rvalue一样,xvalue绑定到rvalue引用,并且像所有glvalue一样,xvalue可以是多态的,非类xvalue可以是cv限定的。
以下表达式是lvalue表达式:
变量、函数或数据成员的名称,不考虑类型,例如std::cin或std::endl。即使变量的类型是rvalue引用,由其名称组成的表达式也是lvalue表达式;函数调用或重载运算符表达式,其返回类型为lvalue引用,例如std::getline(std::cin, str)、std::cout << 1、str1 = str2或++it;a = b、a += b、a %= b和所有其他内置的赋值和复合赋值表达式;++a和--a,内置的前缀递增和前缀递减表达式;*p,内置的间接寻址表达式;a[n]和p[n],内置的下标表达式,除非a是数组rvalue(自C++11以来);a.m,对象成员表达式,除非m是成员枚举器或非静态成员函数,或者a是rvalue且m是非引用类型的非静态数据成员;p->m,指针成员表达式,除非m是成员枚举器或非静态成员函数;a.*mp,对象成员指针表达式,其中a是lvalue,mp是数据成员指针;p->*mp,内置的指向指针成员的指针表达式,其中mp是数据成员指针;a、b,内置的逗号表达式,其中b是lvalue;a ? b : c,三元条件表达式,对于一些b和c(例如,当两者都是相同类型的lvalue时,请参见定义以获取详细信息);字符串字面量,例如“Hello, world!”;转换为lvalue引用类型的强制转换表达式,例如static_cast(x);函数调用或重载运算符表达式,其返回类型为rvalue引用到函数;转换为rvalue引用到函数类型的强制转换表达式,例如static_cast(x)。 (自C++11以来)属性:
与glvalue(下面)相同。可以取lvalue的地址:&++i1和&std::endl是有效的表达式。可修改的lvalue可以用作内置赋值和复合赋值运算符的左操作数。可以使用lvalue初始化lvalue引用;这将一个新名称与由表达式标识的对象关联起来。
仿佛规则
C++编译器可以对程序进行任何更改,只要以下条件仍然成立:
1)在每个序列点上,所有易失对象的值都是稳定的(先前的评估已完成,新的评估未开始)
(自C++11以前)
2)对易失性对象的访问(读取和写入)严格按照它们出现的表达式的语义进行。特别地,它们不会与同一线程上的其他易失性访问重新排序。
(自C++11以后)
3)在程序终止时,写入文件的数据与按原样执行程序完全相同。
4)发送到交互设备的提示文本将在程序等待输入之前显示。
5)如果支持ISO C pragma #pragma STDC FENV_ACCESS并将其设置为ON,则浮点环境(浮点异常和舍入模式)的更改保证被观察到浮点算术运算符和函数调用,就像按原样执行一样,除非
除了转换和赋值之外的任何浮点表达式的结果可能具有不同于表达式类型的浮点类型的范围和精度(请参见FLT_EVAL_METHOD)
尽管如上,任何浮点表达式的中间结果都可以计算为无限范围和精度(除非#pragma STDC FP_CONTRACT为OFF)。
如果您想阅读规格,我认为这些是您需要阅读的规格参考:
引用
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类型限定符
fn()
发出简单的xor eax,eax; ret
?这是否符合标准?这是一个是或否的问题。如果您认为允许,请写下答案,因为根据我理解,当前最受欢迎的答案表明相反。 - geza