在阅读本文之前,请先阅读gcc的相关文档,确保对如何在c中使用汇编语言有个基本的认识。
文档地址为:
https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Using-Assembly-Language-with-C.html#Using-Assembly-Language-with-C
1. 编译器认为asm语句中的 input operands 只是用来读数据的,不会被修改,所以当该asm语句执行完毕之后,后面的代码如果还有地方使用 input operands,则不管在asm语句的汇编代码中有没有修改过 input operands,后面代码使用的 input operands 的值都是原来的值。
2. 如果想要告知编译器 input operands 在asm语句的汇编代码中有被修改过,只能通过将 input operands 绑定到 output operands 的形式。其实本质上编译器还是通过 output operands 得知数据被修改了的,只是此时由于 input operands 和 output operands 使用的是同一寄存器或内存地址,所以相当于编译器也间接得知了 input operands 的修改。
下面来看个具体例子:
代码语言:javascript复制#include <stdio.h>
int inc1(int src) {
asm("add $1, %0" : : "r" (src));
return src;
}
int inc2(int src) {
asm("add $1, %0" : "=r" (src) : "0" (src));
return src;
}
int main(int argc, char *argv[]) {
printf("inc1: %dn", inc1(1));
printf("inc2: %dn", inc2(1));
}
该例子中的inc方法都是将src的值加1,然后再返回,所以理论上来说最终的输出都应该是2。
但我们执行看下结果:
代码语言:javascript复制$ gcc main.c && ./a.out
inc1: 1
inc2: 2
由上可以看到,inc1的结果是1,这是因为编译器认为src只是asm语句的输入,所以它不会被修改,所以在return的时候直接返回了原值。
看下其对应的汇编代码:
代码语言:javascript复制$ gcc -O3 main.c && objdump --disassemble=inc1 a.out
0000000000001180 <inc1>:
1180: 89 f8 mov �i,�x
1182: 83 c7 01 add $0x1,�i
1185: c3 retq
和我们想的一样,return时返回的就是原值。
inc2方法的返回结果是正确的,因为我们把输入绑定到了输出,编译器通过输出可以知道数据被修改过了。
同样看下其汇编代码:
代码语言:javascript复制$ gcc -O3 main.c && objdump --disassemble=inc2 a.out
0000000000001190 <inc2>:
1190: 89 f8 mov �i,�x
1192: 83 c0 01 add $0x1,�x
1195: c3 retq
的确是正确的逻辑。
通过上面的例子我们可以看到,编译器认为asm语句的输入参数是只读的,所以我们在asm语句的汇编代码里也不要去修改输入参数的值,如果非要修改,一定要通过某种方式告知编译器,防止最终的逻辑错误。