首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c语言内嵌汇编代码之volatile究竟何时用

c语言内嵌汇编代码之volatile究竟何时用

作者头像
KINGYT
发布2019-10-14 16:28:52
1.1K0
发布2019-10-14 16:28:52
举报

在阅读本文之前,请先阅读gcc的相关文档,确保对如何在c中使用汇编语言有个基本的认识。

文档地址为:

https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C


1. basic asm 以及没有 output operands 的 extended asm 默认就是volatile 的,所以它们不用显式指定 volatile。

2. volatile 的最终目的是为了防止gcc的某些错误优化,所以它只需要用在那些可能发生错误优化的地方,滥用 volatile 会导致本应该优化的代码无法优化,最终导致性能损耗。

3. gcc如果发现 asm 语句的 output operands 在c语言中没有被使用,则优化后的代码可能会直接移除该语句。

4. gcc如果认为一个c函数中的多条相同的asm语句的 output operands 结果相同,则可能会只保留其中一条asm语句,在该c函数使用到这条 asm语句 output operands 的地方,统一用相同的结果(比如,如果asm语句在循环中,则会提到循环外,如果asm语句在一个c函数中被顺序执行,则只保留第一条asm语句,删除后面的asm语句)。

5. 上面两个优化场景就是gcc可能发生错误优化的地方,这些地方要注意是否要使用 volatile。

6. 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 了。

希望对你有所帮助。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-10-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Linux内核及JVM底层相关技术研究 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档