在阅读本文之前,请先阅读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, %0nt"
"add $1, %0"
: "=r"(dst)
: "r"(src));
return dst;
}
int inc2(int src) {
int dst;
asm("mov %1, %0nt"
"mov $3, %�xnt"
"add $1, %0"
: "=r"(dst)
: "r"(src));
return dst;
}
int inc3(int src) {
int dst;
asm("mov %1, %0nt"
"mov $3, %�xnt"
"add $1, %0"
: "=r"(dst)
: "r"(src)
: "�x");
return dst;
}
int main(int argc, char *argv[]) {
printf("inc1: %dn", inc1(1));
printf("inc2: %dn", inc2(1));
printf("inc3: %dn", 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 �i,�x
1192: 83 c0 01 add $0x1,�x
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 �i,�x
11a2: b8 03 00 00 00 mov $0x3,�x
11a7: 83 c0 01 add $0x1,�x
11aa: c3 retq
从汇编代码角度就看出这个方法的问题了,我们在inc2方法里加入的汇编代码mov $3, �x里使用到了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 �i,%r8d
11b3: b8 03 00 00 00 mov $0x3,�x
11b8: 41 83 c0 01 add $0x1,%r8d
11bc: 44 89 c0 mov %r8d,�x
11bf: c3 retq
确实和我们想的是一样的。
好,到这里我相信大家应该对Clobbers字段的用途都明白了,本文到这里也就结束了。
希望对你们有所帮助。