打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

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

2020.07.15

关注

在阅读本文之前,请先阅读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语句后面的逻辑。这样就保证了所有操作用到的数据都是最新的,是正确的。

下面看个例子:

#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。

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

$ gcc main.c && ./a.outinc1: 2inc2: 4inc3: 2

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

先看inc1方法:

$ gcc -O3 main.c && objdump --disassemble=inc1 a.out0000000000001190 <inc1>: 1190: 89 f8 mov %edi,%eax 1192: 83 c0 01 add $0x1,%eax 1195: c3 retq

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

再看inc2方法:

$ gcc -O3 main.c && objdump --disassemble=inc2 a.out00000000000011a0 <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的汇编代码再确认下:

$ gcc -O3 main.c && objdump --disassemble=inc3 a.out00000000000011b0 <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字段的用途都明白了,本文到这里也就结束了。

希望对你们有所帮助。

完。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
(转)Visual C Inline ASM 内联汇编
【转】C语言内嵌汇编(asm)
Delphi内嵌汇编语言BASM精要(转帖)
【原创】内联汇编
【原创】软件调试基础-01必备汇编基础
AT & T 汇编语法格式(与intel的不同)
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服