热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

是否允许编译器优化本地volatile变量?

如何解决《是否允许编译器优化本地volatile变量?》经验,为你挑选了5个好方法。

是否允许编译器对此进行优化(根据C++ 17标准):

int fn() {
    volatile int x = 0;
    return x;
}

这个?

int fn() {
    return 0;
}

如果是,为什么?如果没有,为什么不呢?


这里有一些关于这个主题的思考:当前编译器编译fn()为放在堆栈上的局部变量,然后返回它.例如,在x86-64上,gcc创建了这个:

mov    DWORD PTR [rsp-0x4],0x0 // this is x
mov    eax,DWORD PTR [rsp-0x4] // eax is the return register
ret    

现在,据我所知,标准并没有说应该将一个局部volatile变量放在堆栈上.所以,这个版本同样好:

mov    edx,0x0 // this is x
mov    eax,edx // eax is the return
ret    

这里,edx商店x.但是现在,为什么要停在这里?由于edxeax均为零,我们可以只说:

xor    eax,eax // eax is the return, and x as well
ret    

我们转变fn()为优化版​​本.这种转变有效吗?如果没有,哪一步无效?



1> Matteo Itali..:

volatile对象的访问被认为是可观察的行为,与I/O完全相同,本地和全局之间没有特别的区别.

符合实施的最低要求是:

volatile严格按照抽象机的规则评估对象的访问.

[...]

这些统称为程序的可观察行为.

N3690,[intro.execution],8

如何准确,这是观察到的是标准的范围之内,直落入实现特定的领土,正是因为I/O,并获得全球volatile对象.volatile意思是"你认为你知道这里发生的一切,但它不是那样的;相信我并做这些事情而不是太聪明,因为我在你的程序中用你的字节做我的秘密事情".这实际上是在[dcl.type.cv]7中解释的:

[注意:volatile是对实现的暗示,以避免涉及对象的激进优化,因为对象的值可能会被实现无法检测到的方式更改.此外,对于某些实现,volatile可能指示访问对象需要特殊的硬件指令.有关详细语义,请参见1.9.一般来说,volatile的语义在C++中与在C中的相同. - 最后的注释]


由于这是最受欢迎的问题,并且通过编辑扩展了问题,因此编辑此答案以讨论新的优化示例将会很愉快.

2> rici..:

这个循环可以通过as-if规则进行优化,因为它没有可观察的行为:

for (unsigned i = 0; i 

这一个不能:

for (unsigned i = 0; i 

第二个循环在每次迭代时都会执行某些操作,这意味着循环需要O(n)时间.我不知道常量是什么,但是我可以测量它然后我有一种忙碌循环的方式(或多或少)已知的时间量.

我可以这样做,因为标准规定必须按顺序进入挥发物.如果编译器决定在这种情况下标准不适用,我想我有权提交错误报告.

如果编译器选择放入looped寄存器,我想我对此没有好的论据.但是,对于每次循环迭代,它仍然必须将该寄存器的值设置为1.


@hyde:事实上,我确实在基准测试中以这种方式使用了volatile,以避免编译器优化掉一个否则什么都不做的循环.所以我真的希望我这是对的:=)

3> Mehrdad..:

尽管充分理解这volatile意味着可观察到的I/O ,但我不赞成多数意见.

如果你有这个代码:

{
    volatile int x;
    x = 0;
}

我相信编译器可以as-if规则下对其进行优化,假设:

    volatile变量不会通过例如指针在外部可见(这显然不是问题,因为在给定范围内没有这样的事情)

    编译器没有为您提供外部访问它的机制 volatile

理由是,由于标准#2,你无论如何都无法观察到差异.

但是,在编译器中,可能不满足条件#2!编译器可能会尝试为您提供有关volatile从"外部" 观察变量的额外保证,例如通过分析堆栈.在这种情况下,行为确实可观察的,因此无法进行优化.

现在的问题是,以下代码是否与上述不同?

{
    volatile int x = 0;
}

我相信我已经在Visual C++中观察到了与优化有关的不同行为,但我不完全确定基于什么理由.初始化可能不算作"访问"?我不确定.如果您感兴趣,这可能值得一个单独的问题,但我相信答案就像我上面解释的那样.



4> berendi - pr..:

从理论上讲,中断处理程序可以

检查返回地址是否属于该fn()函数.它可以通过检测或附加的调试信息访问符号表或源行号.

然后更改值x,该值将存储在堆栈指针的可预测偏移量中.

...因此fn()返回非零值.


** - 1**任何对`fn()`的调用都可以内联.使用MSVC 2017和默认发布模式,它是.然后没有"在`fn()`函数内".无论如何,由于变量是自动存储,因此没有"可预测的偏移".

5> Tezra..:

我只是要为as-if规则和volatile关键字添加详细的参考.(在这些页面的底部,按照"see also"和"References"追溯到原始规格,但我发现cppreference.com更容易阅读/理解.)

特别是,我想让你阅读这一节

volatile对象 - 类型为volatile限定的对象,或volatile对象的子对象,或const-volatile对象的可变子对象.通过volatile限定类型的glvalue表达式进行的每次访问(读取或写入操作,成员函数调用等)都被视为可见的副作用,用于优化(即,在单个执行线程内,volatile访问无法优化或重新排序,具有在易失性访问之前排序或排序的另一个可见副作用.这使得易失性对象适合与信号处理程序通信,但不适用于另一个执行线程,请参阅std :: memory_order ).任何通过非易失性glvalue引用易失性对象的尝试(例如通过引用或指向非易失性类型的指针)都会导致未定义的行为.

因此,volatile关键字专门用于禁用glvalues上的编译器优化.volatile关键字可能影响的唯一可能是return x,编译器可以使用函数的其余部分执行任何操作.

编译器可以优化返回的程度取决于在这种情况下允许编译器优化x访问的程度(因为它不重新排序任何内容,严格来说,不是删除返回表达式.有访问权限) ,但是它正在读取和写入堆栈,应该能够简化.)因此,当我阅读它时,这是允许编译器优化多少的灰色区域,并且可以很容易地双向争论.

旁注:在这些情况下,始终假设编译器将执行您想要/需要的相反操作.您应该禁用优化(至少对于此模块),或尝试根据需要查找更明确的行为.(这也是单元测试如此重要的原因)如果您认为它是一个缺陷,您应该与C++的开发人员一起提出.


这一切仍然很难阅读,所以试图包括我认为相关的内容,以便您自己阅读.

glvalue glvalue表达式是lvalue或xvalue.

属性:

glvalue可以隐式转换为具有左值到右值,数组到指针或函数到指针隐式转换的prvalue.glvalue可以是多态的:它识别的对象的动态类型不一定是表达式的静态类型.glvalue可以具有不完整的类型,表达式允许.


xvalue以下表达式是xvalue表达式:

函数调用或重载的运算符表达式,其返回类型是对象的右值引用,例如std :: move(x); a [n],内置的下标表达式,其中一个操作数是一个数组rvalue; am,对象表达式的成员,其中a是rvalue,m是非引用类型的非静态数据成员; a.*mp,指向对象表达式成员的指针,其中a是rvalue,mp是指向数据成员的指针; 一个 ?b:c,某些b和c的三元条件表达式(详见定义); 一个转换表达式,用于对对象类型的rvalue引用,例如static_cast(x); 临时实现后,指定临时对象的任何表达式.(自C++ 17以来)属性:

与右值相同(下图).与glvalue相同(下图).特别是,像所有rvalues一样,xvalues绑定到rvalue引用,并且像所有glvalues一样,xvalues可能是多态的,非类xvalues可能是cv限定的.


左值以下表达式是左值表达式:

变量,函数或数据成员的名称,无论类型如何,例如std :: cin或std :: endl.即使变量的类型是右值引用,由其名称组成的表达式也是左值表达式; 函数调用或重载的运算符表达式,其返回类型是左值引用,例如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起); am,对象表达式的成员,除非m是成员枚举器或非静态成员函数,或者a是rvalue,m是非引用类型的非静态数据成员; p-> m,指针表达式的内置成员,除非m是成员枚举器或非静态成员函数; a.*mp,指向对象表达式成员的指针,其中a是左值,mp是指向数据成员的指针; p - >*mp,指向表达式成员的内置指针,其中mp是指向数据成员的指针; a,b,内置逗号表达式,其中b是左值; 一个 ?b:c,某些b和c的三元条件表达式(例如,当两者都是相同类型的左值时,但参见详细定义); 字符串文字,例如"Hello,world!"; lvalue引用类型的强制转换表达式,例如static_cast(x); 函数调用或重载的运算符表达式,其返回类型是函数的右值引用; 一个强制转换表达式,用于对函数类型的rvalue引用,例如static_cast(x).(自C++ 11以来)属性:

与glvalue相同(下图).可以采用左值的地址:&++ i 1 和&std :: endl是有效的表达式.可修改的左值可以用作内置赋值和复合赋值运算符的左手操作数.左值可用于初始化左值参考; 这会将新名称与表达式标识的对象相关联.


as-if规则

只要满足以下条件,C++编译器就可以对程序执行任何更改:

1)在每个序列点,所有易失性对象的值都是稳定的(之前的评估是完整的,新的评估没有开始)(直到C++ 11)1)对volatile对象的访问(读取和写入)严格按照语义进行他们出现的表达方式.特别是,它们不会针对同一线程上的其他易失性访问进行重新排序.(从C++ 11开始)2)在程序终止时,写入文件的数据就像程序按写入方式执行一样.3)在程序等待输入之前,将显示发送到交互设备的提示文本.4)如果支持ISO C编译指示#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类型限定符


推荐阅读
  • Introduction(简介)Forbeingapowerfulobject-orientedprogramminglanguage,Cisuseda ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 本文介绍了Java集合库的使用方法,包括如何方便地重复使用集合以及下溯造型的应用。通过使用集合库,可以方便地取用各种集合,并将其插入到自己的程序中。为了使集合能够重复使用,Java提供了一种通用类型,即Object类型。通过添加指向集合的对象句柄,可以实现对集合的重复使用。然而,由于集合只能容纳Object类型,当向集合中添加对象句柄时,会丢失其身份或标识信息。为了恢复其本来面貌,可以使用下溯造型。本文还介绍了Java 1.2集合库的特点和优势。 ... [详细]
  • linux进阶50——无锁CAS
    1.概念比较并交换(compareandswap,CAS),是原⼦操作的⼀种,可⽤于在多线程编程中实现不被打断的数据交换操作࿰ ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
  • 线程漫谈——线程基础
    本系列意在记录Windwos线程的相关知识点,包括线程基础、线程调度、线程同步、TLS、线程池等。进程与线程理解线程是至关重要的,每个进程至少有一个线程,进程是线程的容器,线程才是真正的执行体,线程必 ... [详细]
  • const限定符全解一、const修饰普通变量  intconsta500;  constinta600;  上述两种情况相同,都是声明一个const型的变量,它们 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了Python语言程序设计中文件和数据格式化的操作,包括使用np.savetext保存文本文件,对文本文件和二进制文件进行统一的操作步骤,以及使用Numpy模块进行数据可视化编程的指南。同时还提供了一些关于Python的测试题。 ... [详细]
  • 三、查看Linux版本查看系统版本信息的命令:lsb_release-a[root@localhost~]#lsb_release-aLSBVersion::co ... [详细]
author-avatar
萧逸
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有