C语言的编译,汇编和链接

很多人在第一次接触到C语言的时候是在windows的集成开发环境中学的。把写好的C语言程序放到开发环境中,点一下编译按钮就会帮你生成编译好的可执行程序,这个过程看似非常简单,但是其背后需要经过很多的步骤。其具体步骤可以分为预编译,编译,汇编,链接四部。
下面我们就在Linux中利用gcc编译器来简单的了解一下一个C文件是如何被编译成一个可执行程序的。
首先我们先来写两个简单的C程序
a.c程序

#include 
#define ADD(a,b) (a + b)  // 宏定义
extern int globe_B; // 外部声明b.c中的全局变量
int main(void)
{
	
   printf("globe_B = %d\r\n",globe_B);  // 打印出globe_B的值
   printf("ADD(4,5) = %d\r\n",ADD(4,5));  // 打印出,ADD(4,5)的值
}

b.c程序

int globe_B = 50;

上面两个程序很简单,a.c程序只是简单得打印出globe_B的值和打印出ADD宏定义的值。b.c程序只定义一个globe_B的全局变量。

  • 预编译
    首先来看预编译,预编译的作用是把程序中的宏定义、头文件全部展开、处理条件编译的真假值、去掉注释等作用,最后预编译得到的是一个纯净的C文件。
    在gcc中利用gcc -E xxx.c -o xxx.i得到预编译后的文件。
    下面来对a.c文件进行预编译
# 1 "a.c"
# 1 ""
# 1 ""
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "" 2
# 1 "a.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 27 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/features.h" 1 3 4
# 367 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 1 3 4
# 410 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/bits/wordsize.h" 1 3 4
# 411 "/usr/include/x86_64-linux-gnu/sys/cdefs.h" 2 3 4
# 368 "/usr/include/features.h" 2 3 4
# 391 "/usr/include/features.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 1 3 4
# 10 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 3 4
# 1 "/usr/include/x86_64-linux-gnu/gnu/stubs-64.h" 1 3 4
# 11 "/usr/include/x86_64-linux-gnu/gnu/stubs.h" 2 3 4
# 392 "/usr/include/features.h" 2 3 4
# 28 "/usr/include/stdio.h" 2 3 4

/********************省略***********************/



extern int globe_B;
int main(void)
{

   printf("globe_B = %d\r\n",globe_B);
   printf("ADD(4,5) = %d\r\n",(4 + 5));
}

因为头文件stdio预编译后会展开变成800多行的代码,所以就省略了大部分程序。可以看到a.c被预编译后,注释已经消失了,在第二个printf的ADD宏定义也被展开了,条件编译大家可以自行进行实验。

  • 编译
    编译是在第一步预编译的基础上,对C文件进行语法和语义检查,排除掉有错误的语句,最后得到的是汇编文件
    在gcc中利用gcc -S xxx.i -o xxx.s得到预编译后的文件。
    下面对a.i文件进行编译

	.file	"a.c"
	.section	.rodata
.LC0:
	.string	"globe_B = %d\r\n"
.LC1:
	.string	"ADD(4,5) = %d\r\n"
	.text
	.globl	main
	.type	main, @function
main:
.LFB0:
	.cfi_startproc
	pushq	%rbp
	.cfi_def_cfa_offset 16
	.cfi_offset 6, -16
	movq	%rsp, %rbp
	.cfi_def_cfa_register 6
	movl	globe_B(%rip), %eax
	movl	%eax, %esi
	movl	$.LC0, %edi
	movl	$0, %eax
	call	printf
	movl	$9, %esi
	movl	$.LC1, %edi
	movl	$0, %eax
	call	printf
	movl	$0, %eax
	popq	%rbp
	.cfi_def_cfa 7, 8
	ret
	.cfi_endproc
.LFE0:
	.size	main, .-main
	.ident	"GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
	.section	.note.GNU-stack,"",@progbits

汇编得到的是跟机器架构相关的汇编语言。

  • 汇编
    汇编的作用是把第二步生成的汇编文件转化成目标文件,也就是二进制文件。
    在gcc中用gcc -c xxx.s -o xxx.o生成
    由于生成的文件是二进制文件,所以一般的文本工具不能打开,必须要用一些二进制查看软件才可以,常用的有winhex。
    C语言的编译,汇编和链接_第1张图片
    上面是用winhex打开的a.o二进制文件。

  • 链接
    链接是把第三步中生成的所有目标文件全部链接成一个可执行文件。在链接的时候链接器会把各个文件中的函数,变量一一对应,如果某个变量或函数没有做外部声明就会报链接错误。
    在gcc中用gcc xxx.o -o xxx.out生成
    C语言的编译,汇编和链接_第2张图片
    最后链接出来的可执行文件运行正常。

在实际开发中常用gcc xxx.c -o xxx.out一步到位直接生成可执行文件。

你可能感兴趣的:(c语言)