【问题标题】:Does the -O0 compiler flag have the same effect as the volatile keyword in C?-O0 编译器标志是否与 C 中的 volatile 关键字具有相同的效果?
【发布时间】:2018-05-15 11:20:11
【问题描述】:

当您在 C 中使用 -O0 编译器标志时,您告诉编译器避免任何类型的优化。当您将变量定义为volatile 时,您告诉编译器避免优化该变量。我们可以互换使用这两种方法吗?如果是这样,有什么优点和缺点?以下是我能想到的一些优点和缺点。还有吗?

优点:

  • 使用 -O0 标志很有帮助,如果我们有一个很大的代码库,其中应该声明为 volatile 的变量不是。如果代码显示错误行为,我们可以使用 -O0 标志来消除优化导致问题的可能性,而不是进入代码并查找需要声明为 volatile 的变量.

缺点:

  • -O0 标志将影响整个代码,而volatile 关键字仅影响特定变量。例如,如果我们正在开发一个小型微控制器,这可能是个问题,因为使用 -O0 可能会产生一个大的可执行文件。

【问题讨论】:

  • " 我们可以使用 -O0 标志来消除优化导致问题的可能性" 是的,您可以使用它作为调试技术来查找错误。但不是作为错误修复。
  • @Lundin 我认为您夸大了volatile-O0 之间的相似性。将变量一次读入寄存器并将其保留在那里,多次使用该值而不重新读取它,或者相反地不断更新寄存器中的该值并仅在循环的多次迭代后将其存储一次,这不是一种优化.这种行为不是-O0 可以防止的,但这种行为直接与volatile 的真正含义相矛盾。使用 -O0 在识别代码问题方面可能有用,但它远非确定或详尽。
  • @AndrewHenle 在大多数系统上将变量放在寄存器中本身就是一种优化。无论如何,OP 所指的错误类型很可能是文件范围变量由 ISR 或回调更新,但主程序没有意识到变量已更改,因此相应地优化了代码。这是一个经典且非常讨厌的错误,您可以实际上通过禁用优化来追踪它。一个比较常见的现实世界场景是:“为什么代码在发布版本中中断,而不是在调试版本中中断”。
  • AFAIK 编译器完全可以忽略任何-Ox 标志。只要遵循“好像”规则,在每个级别上进行哪些优化或不进行哪些优化都留给编译器来决定。除非编译器文档有说明,否则无法保证每个变量都会隐含 volatile

标签: c optimization microcontroller volatile


【解决方案1】:

简短的回答是:volatile 关键字 not 表示“不优化”。这是完全不同的东西。它通知编译器该变量可能会被正常程序流程中编译器不可见的东西更改。例如:

  1. 它可以被硬件改变——通常是映射在内存地址空间中的寄存器
  2. 可以通过从未调用的函数进行更改 - 例如中断例程
  3. 变量可以由另一个进程或硬件更改 - 例如多处理器/多核系统中的共享内存

volatile 变量每次使用时都必须从其存储位置读取,并在每次更改时保存。

这里有一个例子:

int foo(volatile int z)
{
    return z + z + z + z;
}

int foo1(int z)
{
    return z + z + z + z;    
}

以及生成的代码(-O0 优化选项)

foo(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov edx, DWORD PTR [rbp-4]
  mov eax, DWORD PTR [rbp-4]
  add edx, eax
  mov eax, DWORD PTR [rbp-4]
  add edx, eax
  mov eax, DWORD PTR [rbp-4]
  add eax, edx
  pop rbp
  ret
foo1(int):
  push rbp
  mov rbp, rsp
  mov DWORD PTR [rbp-4], edi
  mov eax, DWORD PTR [rbp-4]
  sal eax, 2
  pop rbp
  ret

我认为区别很明显。 volatile变量被读取4次,non volatile被读取一次,然后乘以4。

你可以在这里玩自己:https://godbolt.org/g/RiTU4g

在大多数情况下,如果在您打开编译器优化时程序没有运行,您的代码中有一些隐藏的 UB。您应该根据需要进行调试以发现所有这些。正确编写的程序必须运行在任何优化级别。

请记住,“volatile”并不表示或保证一致性和原子性。

【讨论】:

  • 好答案,我只是想补充一点,使用 -O0 时“消失”的任何错误可能不是由于缺少 volatile 关键字引起的。这种行为可能是未初始化的变量、堆栈损坏、缓冲区溢出等。
  • C 标准不同意,它说黑底白字表示 volatile 表示不优化。 C11 5.1.2.3 “如果一个实际的实现可以推断出它的值没有被使用并且没有产生所需的副作用(包括调用函数或访问易失性对象引起的任何副作用),则它不需要评估表达式的一部分。”本文是 C 语言的核心。当然,易失性也意味着其他东西,但它肯定意味着对变量的任何访问都是副作用。
  • @Lundin 这并不意味着不进行优化 - 这种缺乏优化意味着它容易产生副作用。而这句话就是这个意思。
  • 您忘了提及volatile 的另外两个用例:与信号处理程序的通信,以及必须在setjmp/longjmp 之后可见的更改。
  • @JensGustedt 它是 “例如”。还有更多的用例。
【解决方案2】:

编译器标志-O0 绝不可以替代volatile 的正确使用,因为在编译器适当优化时不起作用的代码本身就被破坏了。在有人忘记抛出-O0 开关之前,您不希望损坏的代码让您看起来“正在工作”。

就代码中变量的总百分比而言,即使是大型代码库也需要许多易失性变量,这也是不寻常的。修复缺少volatile 的大型代码库可能需要找到几个需要多个变量的战略位置volatile,并仅修复这几个,而不是采取“霰弹枪法”并禁用所有优化。

【讨论】:

  • 实际上需要 volatile 变量的地方非常有限。正如我在回答中所写,仅当变量容易产生副作用时才需要它们。
  • 小型代码库对 volatile 有大量需求是非常普遍的。即在任何程序中进行任何形式的硬件相关编程。这个问题被标记为“微控制器”,因此可以安全地假设代码库仅用于寄存器映射就需要大约 1000 个 volatile 限定变量。
  • @Lundin 对,内存映射寄存器是我写“需要多个变量的几个战略位置 [in code] volatile”时的意思。
  • 打开我的一个随机的中型嵌入式项目,大约20kib大小和50kLOC,它在78个地方包含关键字volatile,不包括寄存器映射和寄存器定义。很正常的项目。
  • @Lundin 但它仍然很少 - 寄存器和共享内存和多线程数据(包括异常数据)。我没有考虑变量的数量。顺便提一下,volatile 并不意味着连贯性和原子性。
【解决方案3】:

如果我们有一个大的代码库,其中应该声明为 volatile 的变量没有,那么使用 -O0 标志会很有帮助

您可以使用O0调试并修复这种情况下的问题。

如果代码显示错误行为,我们可以使用 -O0 标志来消除优化导致问题的可能性,而不是进入代码并查找需要声明为 volatile 的变量。

这是一个错误的结论。由于缺少volatile 限定符的某些变量,无法保证O0“修复”问题。您的代码中仍然存在问题,需要修复。

您似乎误解了volatile。它本身并不是控制编译器优化的东西。而O0 通常会禁用大多数优化(编译器仍然可以优化)。

总之,不,它们完全不同,服务于不同的目的。因此,毫无疑问可以使用一个而不是另一个或互换使用。

没有理由禁用编译器优化。您需要解决代码中的问题,即将volatile 限定符添加到需要它的变量中。

【讨论】:

  • 调试最好使用-Od
  • @phuclv 我想你的意思是-Og
  • @JL2210 是的,这是一个错字
【解决方案4】:

现有的答案已经很好地涵盖了 volatile,但我认为这个问题的根本原因与 volatile 无关。

如果您的代码使用 -O0 但未启用优化,则您的代码中可能存在各种各样的错误,或者编译器也可能存在错误。这被标记为“微控制器”,我不排除编译器错误。

例如,您可能存在缓冲区溢出或欠载,而优化器只是以稍微不同的方式排列您的代码,从而暴露了该错误。尝试通过静态代码分析器(例如 cppcheck 或 llvm 的静态代码分析)运行您的代码。不过,这是否可行取决于您的代码在微控制器上的具体程度。

最后,取决于编译器,-O0 可能仍会生成将某些值保留在寄存器中一段时间​​的代码,除非使用 volatile,因此在任何情况下我都不会调用 -O0 来替代 volatile。 (这自然是特定于编译器的)。

【讨论】:

  • 代码更可能依赖于低级构造,适合低级编程的高质量编译器(例如 icc)可以支持启用优化,但 gcc 的作者以不支持而自豪除了禁用优化。
猜你喜欢
  • 2014-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-10
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 2010-12-19
相关资源
最近更新 更多