-O0 编译器标志是否与 C 中的 volatile 关键字具有相同的效果?
Does the -O0 compiler flag have the same effect as the volatile keyword in C?
当您在 C 中使用 -O0 编译器标志时,您告诉编译器避免任何类型的优化。当您将变量定义为 volatile
时,您告诉编译器避免优化该变量。我们可以交替使用这两种方法吗?如果是这样,利弊是什么?以下是我能想到的一些优缺点。还有吗?
优点:
- 如果我们有一个很大的代码库,其中应该声明为
volatile
的变量却没有,那么使用 -O0 标志会很有帮助。如果代码显示错误行为,我们无需进入代码并查找哪些变量需要声明为 volatile,我们可以只使用 -O0 标志来消除优化的可能性导致问题。
缺点:
- -O0 标志将影响整个代码,而
volatile
关键字仅影响特定变量。例如,如果我们在小型微控制器上工作,这可能是个问题,因为使用 -O0 可能会产生很大的可执行文件。
编译器标志 -O0
绝不能替代 volatile
的正确使用,因为编译器正确优化后无法运行的代码在本质上是错误的。您不希望出现 "working" 的损坏代码,直到有人忘记按下 -O0
开关。
就代码中变量的总百分比而言,即使是大型代码库也需要许多易失性变量,这是不寻常的。修复缺少 volatile
的大型代码库可能需要找到一些战略位置,其中多个变量需要 volatile
,并仅修复这几个,而不是采用 "shotgun approach" 并禁用所有优化。
Using the -O0 flag is helpful if we have a big code base inside which the variables that should have been declared as volatile, are not
在这种情况下,您可以使用 O0
来 调试 并解决问题。
If the code is showing buggy behavior, instead of going in the code and finding which variables need to be declared as volatile, we can just use the -O0 flag to eliminate the possibility that optimization is causing the problem.
这是一个错误的结论。由于某些变量缺少 volatile
限定符,无法保证 O0
"fixes" 问题。您的代码中仍然存在问题,需要修复。
你好像误会了volatile
。它本身并不是控制编译器优化的东西。而 O0
通常会禁用大多数优化(编译器可以 仍然 优化)。
总而言之,不,它们是完全不同的,服务于不同的目的。因此,不存在相互使用或互换使用的问题。
没有理由禁用编译器优化。您需要解决代码中的问题,即向需要它的变量添加 volatile
限定符。
简短的回答是:volatile
关键字 而不是 意味着 "do not optimize"。这是完全不同的东西。它通知编译器变量可能会被一些在正常程序流程中对编译器不可见的东西改变。例如:
- 它可以被硬件改变——通常是寄存器映射到内存地址space
- 可以通过从未调用的函数更改 - 例如中断例程
- 变量可以被另一个进程或硬件改变——例如多处理器/多核系统中的共享内存
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次,nonvolatile读取一次,然后乘以4.
你可以在这里玩自己:https://godbolt.org/g/RiTU4g
在大多数情况下,如果程序没有 运行 当您打开编译器优化时,您的代码中有一些隐藏的 UB。您应该根据需要进行调试以发现所有这些。正确编写的程序必须 运行 在任何优化级别。
请记住,“volatile”并不意味着或保证一致性和原子性。
现有答案已经很好地涵盖了volatile,但我认为这个问题的根本原因与volatile无关。
如果您的代码使用 -O0 但未启用优化,则您的代码中可能存在各种各样的错误,或者编译器也可能存在错误。这被标记为 "microcontroller",我不排除编译器错误。
例如,您可能遇到缓冲区溢出或欠载,而优化器只是以稍微不同的方式安排您的代码,从而暴露了错误。通过静态代码分析器(例如 cppcheck 或 llvm 的静态代码分析)尝试 运行 您的代码。不过,这是否可行取决于您的代码是如何特定于微控制器的。
最后,根据编译器的不同,-O0 可能仍会生成将某些值保留在寄存器中一段时间的代码,除非使用 volatile,因此在任何情况下我都不会称 -O0 为 volatile 的替代品。 (这自然是特定于编译器的)。
当您在 C 中使用 -O0 编译器标志时,您告诉编译器避免任何类型的优化。当您将变量定义为 volatile
时,您告诉编译器避免优化该变量。我们可以交替使用这两种方法吗?如果是这样,利弊是什么?以下是我能想到的一些优缺点。还有吗?
优点:
- 如果我们有一个很大的代码库,其中应该声明为
volatile
的变量却没有,那么使用 -O0 标志会很有帮助。如果代码显示错误行为,我们无需进入代码并查找哪些变量需要声明为 volatile,我们可以只使用 -O0 标志来消除优化的可能性导致问题。
缺点:
- -O0 标志将影响整个代码,而
volatile
关键字仅影响特定变量。例如,如果我们在小型微控制器上工作,这可能是个问题,因为使用 -O0 可能会产生很大的可执行文件。
编译器标志 -O0
绝不能替代 volatile
的正确使用,因为编译器正确优化后无法运行的代码在本质上是错误的。您不希望出现 "working" 的损坏代码,直到有人忘记按下 -O0
开关。
就代码中变量的总百分比而言,即使是大型代码库也需要许多易失性变量,这是不寻常的。修复缺少 volatile
的大型代码库可能需要找到一些战略位置,其中多个变量需要 volatile
,并仅修复这几个,而不是采用 "shotgun approach" 并禁用所有优化。
Using the -O0 flag is helpful if we have a big code base inside which the variables that should have been declared as volatile, are not
在这种情况下,您可以使用 O0
来 调试 并解决问题。
If the code is showing buggy behavior, instead of going in the code and finding which variables need to be declared as volatile, we can just use the -O0 flag to eliminate the possibility that optimization is causing the problem.
这是一个错误的结论。由于某些变量缺少 volatile
限定符,无法保证 O0
"fixes" 问题。您的代码中仍然存在问题,需要修复。
你好像误会了volatile
。它本身并不是控制编译器优化的东西。而 O0
通常会禁用大多数优化(编译器可以 仍然 优化)。
总而言之,不,它们是完全不同的,服务于不同的目的。因此,不存在相互使用或互换使用的问题。
没有理由禁用编译器优化。您需要解决代码中的问题,即向需要它的变量添加 volatile
限定符。
简短的回答是:volatile
关键字 而不是 意味着 "do not optimize"。这是完全不同的东西。它通知编译器变量可能会被一些在正常程序流程中对编译器不可见的东西改变。例如:
- 它可以被硬件改变——通常是寄存器映射到内存地址space
- 可以通过从未调用的函数更改 - 例如中断例程
- 变量可以被另一个进程或硬件改变——例如多处理器/多核系统中的共享内存
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次,nonvolatile读取一次,然后乘以4.
你可以在这里玩自己:https://godbolt.org/g/RiTU4g
在大多数情况下,如果程序没有 运行 当您打开编译器优化时,您的代码中有一些隐藏的 UB。您应该根据需要进行调试以发现所有这些。正确编写的程序必须 运行 在任何优化级别。
请记住,“volatile”并不意味着或保证一致性和原子性。
现有答案已经很好地涵盖了volatile,但我认为这个问题的根本原因与volatile无关。
如果您的代码使用 -O0 但未启用优化,则您的代码中可能存在各种各样的错误,或者编译器也可能存在错误。这被标记为 "microcontroller",我不排除编译器错误。
例如,您可能遇到缓冲区溢出或欠载,而优化器只是以稍微不同的方式安排您的代码,从而暴露了错误。通过静态代码分析器(例如 cppcheck 或 llvm 的静态代码分析)尝试 运行 您的代码。不过,这是否可行取决于您的代码是如何特定于微控制器的。
最后,根据编译器的不同,-O0 可能仍会生成将某些值保留在寄存器中一段时间的代码,除非使用 volatile,因此在任何情况下我都不会称 -O0 为 volatile 的替代品。 (这自然是特定于编译器的)。