查看 test2.s 中内联汇编代码之前的部分,可以看到:
.file "test2.c"
.globl a
.data
.align 4
.type a, @object
.size a, 4
a:
.long 1
.globl b
.align 4
.type b, @object
.size b, 4
b:
.long 2
.comm c,4,4
变量 a, b 被 .globl 修饰,c 被 .comm 修饰,相当于是把它们导出为全局的,所以可以在汇编代码中使用。
那么问题来了:如果是一个局部变量,在汇编代代码中就不会用 .globl 导出,此时在内联汇编指令中,还可以直接使用吗?
眼见为实,我们把这 3 个变量放到 main 函数的内部,作为局部变量来试一下。
4. test3.c 尝试操作局部变量#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c;
asm("movl a, %eax "
"addl b, %eax "
"movl %eax, c");
printf("c = %d ", c);
return 0;
}
生成汇编代码指令:
gcc -m32 -S -o test3.s test3.c
在 test3.s 中可以看到没有 a, b, c 的导出符号,a 和 b 没有其他地方使用,因此直接把他们的数值复制到栈空间中了:
movl $1, -20(%ebp)
movl $2, -16(%ebp)
我们来尝试编译成可执行程序:
$ gcc -m32 -o test3 test3.c
/tmp/ccuY0TOB.o: In function `main':
test3.c:(.text+0x20): undefined reference to `a'
test3.c:(.text+0x26): undefined reference to `b'
test3.c:(.text+0x2b): undefined reference to `c'
collect2: error: ld returned 1 exit status
编译报错:找不到对 a,b,c 的引用!那该怎么办,才能使用局部变量呢?扩展 asm 格式!
二、扩展 asm 格式
1. 指令格式
asm [volatile] ("汇编指令" : "输出操作数列表" : "输入操作数列表" : "改动的寄存器")
格式说明
汇编指令:与基本asm格式相同;
输出操作数列表:汇编代码如何把处理结果传递到 C 代码中;
输入操作数列表:C 代码如何把数据传递给内联汇编代码;
改动的寄存器:告诉编译器,在内联汇编代码中,我们使用了哪些寄存器;
“改动的寄存器”可以省略,此时最后一个冒号可以不要,但是前面的冒号必须保留,即使输出/输入操作数列表为空。
关于“改动的寄存器”再解释一下:gcc 在编译 C 代码的时候,需要使用一系列寄存器;我们手写的内联汇编代码中,也使用了一些寄存器。
为了通知编译器,让它知道: 在内联汇编代码中有哪些寄存器被我们用户使用了,可以在这里列举出来,这样的话,gcc 就会避免使用这些列举出的寄存器
2. 输出和输入操作数列表的格式
在系统中,存储变量的地方就2个:寄存器和内存。因此,告诉内联汇编代码输出和输入操作数,其实就是告诉它:
向哪些寄存器或内存地址输出结果;
从哪些寄存器或内存地址读取输入数据;
这个过程也要满足一定的格式:
"[输出修饰符]约束"(寄存器或内存地址)
(1)约束
就是通过不同的字符,来告诉编译器使用哪些寄存器,或者内存地址。包括下面这些字符:
a: 使用 eax/ax/al 寄存器;
b: 使用 ebx/bx/bl 寄存器;
c: 使用 ecx/cx/cl 寄存器;
d: 使用 edx/dx/dl 寄存器;
r: 使用任何可用的通用寄存器;
m: 使用变量的内存位置;
先记住这几个就够用了,其他的约束选项还有:D, S, q, A, f, t, u等等,需要的时候再查看文档。
(2)输出修饰符
顾名思义,它使用来修饰输出的,对输出寄存器或内存地址提供额外的说明,包括下面4个修饰符:
+:被修饰的操作数可以读取,可以写入;
=:被修饰的操作数只能写入;
%:被修饰的操作数可以和下一个操作数互换;
&:在内联函数完成之前,可以删除或者重新使用被修饰的操作数;
语言描述比较抽象,直接看例子!
3. test4.c 通过寄存器操作局部变量#include <stdio.h>
int main()
{
int data1 = 1;
int data2 = 2;
int data3;
asm("movl %%ebx, %%eax "
"addl %%ecx, %%eax"
: "=a"(data3)
: "b"(data1),"c"(data2));
printf("data3 = %d ", data3);
return 0;
}
有 2 个地方需要注意一下啊:
在内联汇编代码中,没有声明“改动的寄存器”列表,也就是说可以省略掉(前面的冒号也不需要);
扩展asm格式中,寄存器前面必须写 2 个%;
代码解释:
"b"(data1),"c"(data2) ==> 把变量 data1 复制到寄存器 %ebx,变量 data2 复制到寄存器 %ecx。这样,内联汇编代码中,就可以通过这两个寄存器来操作这两个数了;
"=a"(data3) ==> 把处理结果放在寄存器 %eax 中,然后复制给变量data3。前面的修饰符等号意思是:会写入往 %eax 中写入数据,不会从中读取数据;
通过上面的这种格式,内联汇编代码中,就可以使用指定的寄存器来操作局部变量了,稍后将会看到局部变量是如何从经过栈空间,复制到寄存器中的。
生成汇编代码指令:
gcc -m32 -S -o test4.s test4.c
汇编代码 test4.s 如下:
movl $1, -20(%ebp)
movl $2, -16(%ebp)
movl -20(%ebp), %eax
movl -16(%ebp), %edx
movl %eax, %ebx
movl %edx, %ecx
#APP
# 10 "test4.c" 1
movl %ebx, %eax
addl %ecx, %eax
# 0 "" 2
#NO_APP
movl %eax, -12(%ebp)
可以看到,在进入手写的内联汇编代码之前:
把数字 1 通过栈空间(-20(%ebp)),复制到寄存器 %eax,再复制到寄存器 %ebx;
把数字 2 通过栈空间(-16(%ebp)),复制到寄存器 %edx,再复制到寄存器 %ecx;
这 2 个操作正是对应了内联汇编代码中的“输入操作数列表”部分:"b"(data1),"c"(data2)。
在内联汇编代码之后(#NO_APP 之后),把 %eax 寄存器中的值复制到栈中的 -12(%ebp) 位置,这个位置正是局部变量 data3 所在的位置,这样就完成了输出操作。