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等都是解释型语言。
 
解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执 行一次都要翻译一次

C语言编译过程的更多相关文章

  1. C语言编译过程及数据类型

    写在前面 C语言可以称得上是高级语言中的低级语言,接下来一段时间,我会写一下文章关于c语言,把它的神秘面纱一 一揭开.下面主要是c语言的C语言编译过程及数据类型 源文件编译过程 为了使计算机能执行高级 ...

  2. C语言编译过程以及gcc编译参数

    1.1       C语言编译过程,gcc参数简介 1.1.1          C语言编译过程 一.gcc - o a a.c -o:指定文件输出名字 二.C语言编译的过程: 1.1.1       ...

  3. 转 C语言编译过程简介

    C语言编译过程简介 C语言编译过程简介 刚开始接触编程的时候,只知道照书敲敲代码,一直都不知道为什么在windows平台下代码经过鼠标那样点击几下,程序的结果就会在那个黑色的屏幕上.现在找了个机会将C ...

  4. GCC 使用-C语言编译过程

    任何一种高级语言,要想在机器上执行,必须翻译为机器能读懂的机器语言.编译器就相当于翻译官,将高级语言翻译为机器语言. GCC 最初只用了编译 C 语言程序,全称是 GNU C Compiler.后来扩 ...

  5. Go 语言编译过程

    走进Golang之编译器原理_大愚Talk-CSDN博客 https://blog.csdn.net/hel12he/article/details/103061921 go编译器 - 知乎 http ...

  6. go语言编译过程概述

    go语言编译过程概述 总结自<go语言设计与实现> 名词解释: 中间代码 中间代码是编译器或者虚拟机使用的语言,它可以来帮助我们分析计算机程序.在编译过程中,编译器会在将源代码转换到机器码 ...

  7. C语言编译过程详解

    前言 C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程. 编写hello world C程序: // hello.c #include ...

  8. C语言编译过程(转)

    内容摘要 : C语言编译的整个过程是非常复杂的,里面涉及到的编译器知识.硬件知识.工具链知识都是非常多的,深入了解整个编译过程对工程师理解应用程序的编写是有很大帮助的,希望大家可以多了解一些,在遇到问 ...

  9. C语言基础(21)-C语言编译过程及GCC参数简介

    任何C语言的编译过程可分为以下三部分: 一.预编译 在C语言中,以#开头的语句又叫预编译指令.预编译主要做以下两件事情: 1.将#include包含的头文件做简单的文本替换: 2.将代码中的注释删除. ...

随机推荐

  1. 终于解决:升级至.NET 4.6.1后VS2015生成WCF客户端代理类的问题

    在Visual Studio 2015中将一个包含WCF引用的项目的targetFramework从4.5改为4.6.1的时候,VS2015会重新生成WCF客户端代理类.如果WCF引用配置中选中了&q ...

  2. ASP.NET MVC请求处理管道生命周期的19个关键环节(1-6)

    ASP.NET和ASP.NET MVC的HttpApplication请求处理管道有共同的部分和不同之处,本系列将体验ASP.NET MVC请求处理管道生命周期的19个关键环节. ①以IIS6.0为例 ...

  3. NSDate NSString相互转化

    时间戳是经常用到的,今天就总结一下 //设置转化格式 NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; [formatter s ...

  4. 大叔也说Xamarin~Android篇~为HttpClient共享Session,android与api的session共享机制

    回到目录 杂谈 在进行android进行开发时,我们的数据一般通过接口来获收,这里指的接口泛指web api,webservice,wcf,web应用程序等:它们做为服务端与数据库进行直接通讯,而AP ...

  5. [Java面试九]脚本语言知识总结.

    核心内容概述 1.JavaScript加强,涉及到ECMAScript语法.BOM对象.DOM对象以及事件. 2.Ajax传统编程. 3.jQuery框架,九种选择器为核心学习内容 4.JQuery ...

  6. Atitit usrQBM1603短信验证码规范

    Atitit usrQBM1603短信验证码规范 短信验证码扩展至短信服务和验证码服务1 主要方法1 参考模板1 短信验证码扩展至短信服务和验证码服务 主要方法 Line 27: public cla ...

  7. Android Activity 启动模式和任务栈

    在了解了基本的Activity的生命周期后,我们能够很好的在一个Activity上面做相关的业务.但是这是不够的,因为Android通过任务栈来保存整个APP的Activity,合理的调度任务栈才能够 ...

  8. mysql如果数据不存在,则插入新数据,否则更新的实现方法

    mysql如果数据不存在,则插入新数据,否则更新的实现方法 //如果不存在,则插入新数据 $sql = "INSERT INTO {$ecs->table('cat_lang')} ( ...

  9. Java中的数是用补码表示的检验

    一.基本介绍(关于下列五个定义来自http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html#!comments,谢原 ...

  10. 那些年我们写过的T-SQL(中篇)

    中篇的重点在于,在复杂情况下使用表表达式的查询,尤其是公用表表达式(CTE),也就是非常方便的WITH AS XXX的应用,在SQL代码,这种方式至少可以提高一倍的工作效率.此外开窗函数ROW_NUM ...