C语言编译过程

简介:   GCC编译C源码有四个步骤: 预处理-----> 编译 ----> 汇编 ----> 链接    一、 编译和链接的流程 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。

 

 

GCC编译C源码有四个步骤: 

预处理-----> 编译 ----> 汇编 ----> 链接 

 

一、 编译和链接的流程

C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接。编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程。链接是把目标文件、操作系统的启动代码和用到的库文件进行组织形成最终生成可执行代码的过程。过程图解如下:


从图上可以看到,整个代码的编译过程分为编译和链接两个过程,编译对应图中的大括号括起的部分,其余则为链接过程。

下面是对应的GNU工具链生成文件的过程:


说明:这些后缀并不是必须的,这只是常见的后缀方式,对于C++,对应一般为.cpp,.ii,.s,.o,exec/.so等。参考http://zhidao.baidu.com/question/89958523.html。当然,本质上,.c/.i/.s文件都是文本文件,可以直接查看内容的。.o/exec/.so等文件需要工具查看一些信息。


二、. 编译的三个阶段:

1. 预处理阶段
在正式的编译阶段之前进行,预处理阶段将根据已放置在文件中的预处理指令来修改源文件的内容。预处理处理的主要内容包括:
A、宏定义,如#define PI 3.14
简单来说就是进行宏替换。
B、条件编译,如#ifdef,#ifndef,#else,#endif等等
预编译程序根据这些指令,将不必要参与编译的代码过滤掉。
C、头文件包含,如#include <filename.h>, #include "filename.h"
D、特殊符号,预编译程序可以识别一些特殊的符号
典型的就是__LINE__、__FILE__等编译器内置的预定义宏了。

总之,预处理的核心工作就是“替换”。当然,预处理也会去掉代码中的注释内容,总之,预处理的目的就是简化编译阶段扫描的内容。

对应的GCC选项:-E(预处理,但不编译),输出为对.c文件预处理后的结果,默认输出到控制台,使用-o指定输出到文件。对于GNU工具链,其提供了独立的预处理器,为cpp

举例如下:

// File: test.h
#define MACRO_A		1
int foo();
// File: test.cpp
//#include <stdio.h>
#include "test.h"

#define MACRO_B		"macro_B"

int main()
{
	printf("MACRO_A  is :%d; MACRO_B is: %s\n", foo(), MACRO_B);
	return 1;
}

int foo()
{
	return MACRO_A;
}

编译:

gcc test.cpp -E -o out.ii

说明:这里的test.cpp中,注释了#include <stdio.h>进行预处理是不会报错的,只有在编译的时候才会提示printf错误。下面是输出的out.ii的内容:

# 1 "test.cpp"
# 1 "<built-in>"
# 1 "<命令行>"
# 1 "test.cpp"


# 1 "test.h" 1


int foo();
# 4 "test.cpp" 2



int main()
{
 printf("MACRO_A  is :%d; MACRO_B is: %s\n", foo(), "macro_B");
 return 1;
}

int foo()
{
 return 1;
}

说明:这里可以看到,里面有一些#开头的信息,这些信息在编译阶段是会忽略(不会被扫描)的,这些行的内容是一些记录一些行信息和文件的信息等等。之所以上面把#include <stdio.h>注释掉,是因为加入这一句之后,stdio.h中的内容也会被替换到out.ii中,这样内容太多,不适合贴在这里。

如果使用cpp进行预处理,那么就是(GCC内部还是调用cpp处理的):

cpp test.cpp -o out.ii


2. 编译和优化
经过预编译得到的输出文件中,只有常量;如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,}, +,-,*,\等等。
编译就是指传统的编译原理中提到的内容了,词法分析、语法分析、中间代码生成、汇编代码生成等。(说明:这里的中间代码生成是编译原理中的中间表达式的代码,不是.o目标文件,好像有时候也把目标文件称之为中间文件,这里区分一下)
优化也是编译原理中涉及的内容,优化涉及的内容很多,优化可以是对中间代码本身的优化或者目标代码的生成进行(即由中间代码生成汇编代码的过程或者生成目标文件的过程),关于优化,这里不深入探讨。
总之,这里说的编译的过程,就是由预处理的文件,编译优化得到汇编文件的过程。

对应的GCC选项:-S(编译,但不汇编)。输出为.s文件。说明:gcc编译可以以源文件作为输入,其实也是可以直接用预处理的.i文件作为输入的。当然,唯一要注意的是如果原来的源文件为cpp而输出是.i不是.ii,那么.i作为输入的时候,最好使用g++,否则可能编译有问题(gcc/g++会根据文件后缀判断是c还是c++的文件,所以对应就好了)。

对于GNU工具链,其提供了独立的预编译器,为ccl(好像没有这个命令呢?总之,ccp和ccl几乎不会用到,直接用gcc就可以了)

还是上面的例子(把#include<stdio.h>取消注释):

$ gcc test.cpp -E -o test.ii
$ gcc test.cpp -S
$ gcc test.ii -S
$

说明:其中-S默认输出到文件中,所以不使用-o也是可以输出到文件中的。 


3. 汇编

汇编实际上指把汇编语言代码翻译成目标机器指令的过程。其输出就是目标文件了(.o)。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:
代码段:该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。
数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

说明:汇编之后,编译的过程就完成了,得到了目标文件(.o)。

对应的GCC选项:-c(汇编,但不链接)

还是上面的例子(用.ii/.s/.cpp作为输入都是可以的,gcc会根据后缀知道输入是什么文件,从而在此基础上继续处理):

$ gcc test.ii -c
$ gcc test.s -c
$ gcc test.cpp -c
$ 

其中gcc test.s -c,就是直接编译汇编文件,而gcc test.cpp -c就会从预处理开始进行处理了。对于GNU工具链,其提供了独立的汇编器,为as。也可以使用as编译汇编文件(当然,也只能编译汇编文件了),上面的gcc test.s -c相当于:

$ as test.s -o test.o

(不使用-o test.o,那么输出默认为a.o)

总结:编译主要包括预处理、编译、汇编三个阶段,通过GCC的选项能控制GCC处理到某一步之后就停止,当然,实际的GCC处理,可能不一定是完全的一步一步的处理的,可能会有一些优化的处理方式。


三、链接

由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。
例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够诶操作系统装入执行的统一整体。

(1)静态链接
在这种链接方式下,函数的代码将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。
 (2) 动态链接
在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

对于可执行文件中的函数调用,可分别采用动态链接或静态链接的方法。使用动态链接能够使最终的可执行文件比较短小,并且当共享对象被多个进程使用时能节约一些内存,因为在内存中只需要保存一份此共享对象的代码。但并不是使用动态链接就一定比使用静态链接要优越。在某些情况下动态链接可能带来一些性能上损害。


四、整体过程

下面是整体的过程:


 

解释型语言:

相对于编译型语言存在的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行。比如 Python/JavaScript / Perl /Shell等都是解释型语言。
 
解释型语言:程序不需要 编译,程序在运行时才翻译成 机器语言,每执 行一次都要翻译一次
目录
相关文章
|
1月前
|
存储 自然语言处理 编译器
C语言从入门到实战——编译和链接
在C语言中,编译和链接是将源代码转换为可执行文件的两个主要步骤。 编译过程包括以下步骤: 1. 预处理:将源代码中的预处理指令(如`#include`和`#define`)替换为实际的代码。 2. 编译:将预处理后的代码转换为汇编语言。 3. 汇编:将汇编语言转换为机器码指令。 链接过程包括以下步骤: 1. 目标文件生成:将每个源文件编译后生成的目标文件(`.o`或`.obj`)进行合并,生成一个总的目标文件。 2. 符号解析:查找并解析目标文件中的所有符号(例如全局变量和函数名),以确保每个符号都有一个唯一的地址。 3. 地址重定位:根据符号表中符号的地址信息,将目标文件中的所有地址引用
39 0
|
1月前
|
机器学习/深度学习 人工智能 算法
21.C语言:if语句编译选择结构举例
21.C语言:if语句编译选择结构举例
13 0
|
4月前
|
存储 自然语言处理 编译器
C语言编译和链接
ANSI C(American National Standards Institute C)是指按照美国国家标准协会(ANSI)定的C语言标准。在1989年,ANSI制定了一套C语言标准,该标准通常被称为ANSI C或C89(为了区别于后续的标准C99)。ANSI C标准被国际标准化组织(ISO)接受,并以ISO/IEC 9899:1990的形式发布。所以,ANSI C和ISO C代表的是同一种标准。
|
10天前
|
存储 自然语言处理 编译器
编译和链接---C语言
编译和链接---C语言
|
1月前
|
存储 机器学习/深度学习 自然语言处理
【进阶C语言】编译与链接、预处理符号详解
【进阶C语言】编译与链接、预处理符号详解
23 0
|
1月前
|
编译器 Linux C语言
程序环境和预处理(含C语言程序的编译+链接)--2
程序环境和预处理(含C语言程序的编译+链接)--2
30 5
|
1月前
|
存储 编译器 程序员
程序环境和预处理(含C语言程序的编译+链接)--1
程序环境和预处理(含C语言程序的编译+链接)--1
24 0
|
2月前
|
C语言
C语言多文件编译、结构体、枚举及联合
C语言多文件编译、结构体、枚举及联合
20 0
|
3月前
|
编译器 Linux vr&ar
C语言静态编译和动态编译
C语言静态编译和动态编译
73 0
|
3月前
|
存储 缓存 Linux
C语言编译过程——预处理、编译汇编和链接详解
C语言编译过程——预处理、编译汇编和链接详解