我们在c1.c文件中定义一个变量
int i = 3;
在c2.c文件中使用这个变量
#include<stdio.h>
int main()
{
printf("%d\n",i);
}
编译c2.c的时候会报错,因为在编译阶段,可见性仍局限于各自的文件,编译c2.c文件时觉察不到c1.c文件中已经定义了i。
虽然编译器的目光不够长远,但是我们可以给它提示,帮助它来解决上面出现的问题。这就是extern的作用了。
#include<stdio.h>
extern int i;
int main()
{
printf("%d\n",i);
}
extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
gcc -c c1.c
gcc -c c2.c
gcc c1.o c2.o -o test
程序正常运行。
基于上面的原理,我们完全可以将一个变量或一个函数用汇编语言写,在c语言中用extern声明一下,就可以调用啦。
C和汇编参数传递规则:
举个例子,在a.s中用汇编定义一个函数:
.globl _add
_add:
pushq %rbp
movq %rsp, %rbp
movl %edi, -4(%rbp)
movl %esi, -8(%rbp)
movl -4(%rbp), %eax
addl -8(%rbp), %eax
popq %rbp
retq
然后在b.c中用extern声明add是外部函数:
#include<stdio.h>
extern int add(int a,int b);
int main()
{
int i;
i = add(5,6);
printf("%d\n",i);
}
编译
gcc -c a.s
gcc -c b.c
gcc a.o b.o -o test
程序可以正常执行。
如果你有疑惑,可以将b.c转成汇编:
gcc -S -masm=intel -fno-asynchronous-unwind-tables b.c -o b.s
在汇编中可以看到,c语言调用add函数无非就是call _add
,若用汇编实现这个函数,只需要汇编的标号为_add
即可。
.section __TEXT,__text,regular,pure_instructions
.build_version macos, 10, 16 sdk_version 11, 0
.intel_syntax noprefix
.globl _main ## -- Begin function main
.p2align 4, 0x90
_main: ## @main
## %bb.0:
push rbp
mov rbp, rsp
sub rsp, 16
mov edi, 5
mov esi, 6
call _add
mov dword ptr [rbp - 4], eax
mov esi, dword ptr [rbp - 4]
lea rdi, [rip + L_.str]
mov al, 0
call _printf
xor ecx, ecx
mov dword ptr [rbp - 8], eax ## 4-byte Spill
mov eax, ecx
add rsp, 16
pop rbp
ret
## -- End function
.section __TEXT,__cstring,cstring_literals
L_.str: ## @.str
.asciz "%d\n"
.subsections_via_symbols
同样的,汇编调用C语言也是这个道理。
GCC 提供了内联汇编语法,可以直接在C语言中使用汇编指令。GCC扩展内联汇编的基本格式是:
asm volatile (
Assembler Template
: Output Operands /* optional */
: Input Operands /* optional */
)
例如:
int main()
{
int a=10, b;
asm volatile (
"movl %1, %%eax; \n"
"movl %%eax, %0;"
:"=r"(b) /* output */
:"r"(a) /* input */
);
}
汇编的意思是将变量a的值放到eax寄存器,然后将eax的值放到b变量中。
%0,%1指输入输出列表中的变量,本例中%0代表b,%1代表a。由于这些样板操作数的前缀使用了“%”,因此,在用到具体的寄存器时就在前面加两个“%”,如%%eax。
输入输出列表中的r是指约束条件,或者说是“变量类型”,输出的约束条件前面要加"=",常用约束条件列表:
字母 | 含义 |
---|---|
m | 内存单元 |
i | 常数(0~31) |
a, b, c, d | 寄存器eax, ebx, ecx,edx |
q,r | 动态分配的寄存器 |
g | eax,ebx,ecx,edx或内存变量 |
数字"0" | 指定与第0个变量相同的约束 |
最后,Intel汇编与AT&T汇编格式不同,他们是可以替换的,这里给出参考:
+------------------------------+------------------------------------+
| Intel Code | AT&T Code |
+------------------------------+------------------------------------+
| mov eax,1 | movl $1,%eax |
| mov ebx,0ffh | movl $0xff,%ebx |
| int 80h | int $0x80 |
| mov ebx, eax | movl %eax, %ebx |
| mov eax,[ecx] | movl (%ecx),%eax |
| mov eax,[ebx+3] | movl 3(%ebx),%eax |
| mov eax,[ebx+20h] | movl 0x20(%ebx),%eax |
| add eax,[ebx+ecx*2h] | addl (%ebx,%ecx,0x2),%eax |
| lea eax,[ebx+ecx] | leal (%ebx,%ecx),%eax |
| sub eax,[ebx+ecx*4h-20h] | subl -0x20(%ebx,%ecx,0x4),%eax |
+------------------------------+------------------------------------+