前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >c语言内嵌汇编代码之Clobbers的用途到底是什么

c语言内嵌汇编代码之Clobbers的用途到底是什么

作者头像
KINGYT
发布2019-10-14 16:29:09
2.8K0
发布2019-10-14 16:29:09
举报

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

文档地址为:

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


1. Clobbers 是一个以逗号分隔的寄存器列表(该列表中还可以存放一些特殊值,用于表示一些特殊用途)。

2. 它的目的是为了告知编译器,Clobbers 列表中的寄存器会被该asm语句中的汇编代码隐性修改。

3. 由于 Clobbers 里的寄存器会被asm语句中的汇编代码隐性修改,编译器在为 input operands 和 output operands 挑选寄存器时,就不会使用 Clobbers 里指定的寄存器,这样就避免了发生数据覆盖等逻辑错误。

4. 通俗来讲,Clobbers 的用途就是为了告诉编译器,我这里指定的这些寄存器在该asm语句的汇编代码中用了,你在编译这条asm语句时,如果需要用到寄存器,别用我这里指定的这些,否则就都乱了。

5. Clobbers 里的特殊值可以为 cc,用于表示该平台的 flags 寄存器会被隐性修改(比如 x86 平台的 eflags 寄存器)。

6. Clobbers 里的特殊值也可以为 memory,用于表示某些内存数据会被隐性使用或隐性修改,所以在执行这条asm语句之前,编译器会保证所有相关的、涉及到内存的寄存器里的内容会被刷到内存中,然后再执行这条asm语句。在执行完这条asm语句之后,这些寄存器的值会再被重新load回来,然后再执行这条asm语句后面的逻辑。这样就保证了所有操作用到的数据都是最新的,是正确的。

下面看个例子:

代码语言:javascript
复制
#include <stdio.h>

int inc1(int src) {
  int dst;

  asm("mov %1, %0\n\t"
      "add $1, %0"
      : "=r"(dst)
      : "r"(src));

  return dst;
}

int inc2(int src) {
  int dst;

  asm("mov %1, %0\n\t"
      "mov $3, %%eax\n\t"
      "add $1, %0"
      : "=r"(dst)
      : "r"(src));

  return dst;
}

int inc3(int src) {
  int dst;

  asm("mov %1, %0\n\t"
      "mov $3, %%eax\n\t"
      "add $1, %0"
      : "=r"(dst)
      : "r"(src)
      : "%eax");

  return dst;
}

int main(int argc, char *argv[]) {
  printf("inc1: %d\n", inc1(1));
  printf("inc2: %d\n", inc2(1));
  printf("inc3: %d\n", inc3(1));
}

上面代码中三个inc方法的意图都是对src参数加1,然后再返回,所以理论上来说,最终的输出会是三个2。

但真是这样吗?让我们来运行看看:

代码语言:javascript
复制
$ gcc main.c && ./a.out
inc1: 2
inc2: 4
inc3: 2

inc2方法居然返回的不是2,而是4,奇怪吧。但为什么呢,让我们反编译看下。

先看inc1方法:

代码语言:javascript
复制
$ gcc -O3 main.c && objdump --disassemble=inc1 a.out
0000000000001190 <inc1>:
    1190:  89 f8                  mov    %edi,%eax
    1192:  83 c0 01               add    $0x1,%eax
    1195:  c3                     retq

以汇编角度看,这个方法没什么问题,就是先把src的值拷贝到eax寄存器中,然后再将eax寄存器的值加1,最后将eax中的结果返回给上层。

再看inc2方法:

代码语言:javascript
复制
$ gcc -O3 main.c && objdump --disassemble=inc2 a.out
00000000000011a0 <inc2>:
    11a0:  89 f8                  mov    %edi,%eax
    11a2:  b8 03 00 00 00         mov    $0x3,%eax
    11a7:  83 c0 01               add    $0x1,%eax
    11aa:  c3                     retq

从汇编代码角度就看出这个方法的问题了,我们在inc2方法里加入的汇编代码mov $3, %eax里使用到了eax寄存器,而inc2方法里的asm语句中的其他汇编代码用到的寄存器居然也是eax,这样就导致了我们加入的mov语句把eax里原来的值给覆盖掉了,所以最终返回了4,而不是2。

但是,我们既然已经在汇编代码里用到了eax寄存器,为什么gcc还会分配eax给其他汇编代码用呢?

这是因为,gcc在编译时,根本就不会分析asm里的汇编代码,所以它也就不知道我们已经使用了eax寄存器,所以才导致的最终冲突。

那我们怎样才能告诉gcc,我们已经用了eax寄存器,让它别再用了呢?

对,就是通过 Clobbers。

看下上面的inc3方法,它在Clobbers字段位置指定了eax,即告知gcc,eax寄存器已经被我们用了,你就不要再用了,所以inc3方法返回的结果是正确的。

看下inc3的汇编代码再确认下:

代码语言:javascript
复制
$ gcc -O3 main.c && objdump --disassemble=inc3 a.out
00000000000011b0 <inc3>:
    11b0:  41 89 f8               mov    %edi,%r8d
    11b3:  b8 03 00 00 00         mov    $0x3,%eax
    11b8:  41 83 c0 01            add    $0x1,%r8d
    11bc:  44 89 c0               mov    %r8d,%eax
    11bf:  c3                     retq

确实和我们想的是一样的。

好,到这里我相信大家应该对Clobbers字段的用途都明白了,本文到这里也就结束了。

希望对你们有所帮助。

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

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

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

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

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