在阅读本文之前,请先阅读 gcc 的相关文档,确保对如何在 c 中使用汇编语言有个基本的认识。
文档地址为:
https://gcc.gnu.org/onlinedoc…
- basic asm 以及没有 output operands 的 extended asm 默认就是 volatile 的,所以它们不用显式指定 volatile。
- volatile 的最终目的是为了防止 gcc 的某些错误优化,所以它只需要用在那些可能发生错误优化的地方,滥用 volatile 会导致本应该优化的代码无法优化,最终导致性能损耗。
- gcc 如果发现 asm 语句的 output operands 在 c 语言中没有被使用,则优化后的代码可能会直接移除该语句。
- gcc 如果认为一个 c 函数中的多条相同的 asm 语句的 output operands 结果相同,则可能会只保留其中一条 asm 语句,在该 c 函数使用到这条 asm 语句 output operands 的地方,统一用相同的结果(比如,如果 asm 语句在循环中,则会提到循环外,如果 asm 语句在一个 c 函数中被顺序执行,则只保留第一条 asm 语句,删除后面的 asm 语句)。
- 上面两个优化场景就是 gcc 可能发生错误优化的地方,这些地方要注意是否要使用 volatile。
- volatile 无法保证多条 asm 语句在优化前后顺序相同,如果要保证顺序,可以把多条 asm 语句中的汇编代码都写到一个 asm 语句里。
下面用示例讲解下相关概念:
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
void do_check(uint32_t dwSomeValue) {
uint32_t dwRes;
asm("bsfl %1,%0" : "=r"(dwRes) : "r"(dwSomeValue) : "cc");
assert(dwRes == 3);
}
int main(int argc, char *argv[]) {do_check(8); }
编译后再以汇编形式查看 do_check 方法:
$ gcc -O3 main.c && objdump --disassemble=do_check a.out
0000000000001190 <do_check>:
1190: 0f bc ff bsf %edi,%edi
1193: 83 ff 03 cmp $0x3,%edi
1196: 75 01 jne 1199 <do_check+0x9>
1198: c3 retq
1199: 50 push %rax
119a: e8 c1 ff ff ff callq 1160 <do_check.part.0>
由于 assert 宏中使用了 asm 语句中的输出参数 dwRes,所以即使 asm 语句没有指定 volatile,do_check 方法也是在正常执行的。
下面看下把 assert 方法去掉之后的 do_check 汇编代码:
$ gcc -O3 -D NDEBUG main.c && objdump --disassemble=do_check a.out
0000000000001130 <do_check>:
1130: c3 retq
由上可见,因为我们在执行 gcc 时加了 -D NDEBUG 参数,定义了 NDEBUG 宏,所以上面的 assert 宏最终会变为空指令。
也就是说,do_check 方法中没有任何地方在使用 asm 语句中的输出参数 dwRes,所以 gcc 就会在优化后的代码中删除掉该 asm 语句,所以上面的 do_check 方法最终变成了空方法。
那我们将上面的 asm 语句加上 volatile 再试下:
void do_check(uint32_t dwSomeValue) {
uint32_t dwRes;
asm volatile("bsfl %1,%0" : "=r"(dwRes) : "r"(dwSomeValue) : "cc");
assert(dwRes == 3);
}
编译后再以汇编形式查看 do_check 方法:
$ gcc -O3 -D NDEBUG main.c && objdump --disassemble=do_check a.out
0000000000001130 <do_check>:
1130: 0f bc ff bsf %edi,%edi
1133: c3 retq
由上可以看到,这次即使指定了 -D NDEBUG 参数,使 assert 宏变成了空操作,导致 do_check 方法中没有任何地方使用 dwRes 变量,但由于 volatile 的存在,该 asm 语句还是没有被优化掉。
通过上面的例子,我们就可以看到 volatile 是如何防止 gcc 优化代码的,但是在上面的例子中,该优化是一个正确的优化,所以不应该加 volatile。
如果有其他的 asm 语句,虽然它的输出参数没有被使用,但也不应该被优化掉,这个时候就应该使用 volatile 了。
希望对你有所帮助。
完。
更多原创文章,请关注我微信公众号: