对于平常应用程序的开发,很少有人会关注编译和链接的过程,因为我们使用的工具一般都是流行的集成开发环境(IDE),比如 Visual Studio、Dev C++、C-Free 等。这些功能强大的 IDE 通常将编译和链接合并到一起,也就是构建(Build)或运行(Run)。即使在 Linux 下使用命令行来编译一个源文件,简单的一句$gcc demo.c也包含了非常复杂的过程。

虽然 IDE 提供的默认配置、编译和链接参数对于大部分应用程序来说已经足够使用了,但是作为学习者,我们还是要刨根问底,弄清从源代码生成可执行文件的内部机理,不要被 IDE 提供的强大功能所迷惑。

C语言经典的“Hello World”小程序几乎是每个程序员闭着眼睛都能写出来的,基本成了入门教程和开发环境的默认标准,代码如下:

  1. #include <stdio.h>
  2. int main(){
  3. printf("Hello World\n");
  4. return 0;
  5. }

如果在 Windows 下使用 Visual Studio 来编译,那么可以直接点击运行(Run)按钮或者构建(Build)按钮,在工程目录下就会看到生成的 .exe 程序。

如果在 Linux 下使用 GCC 来编译,使用最简单的$gcc demo.c命令,就可以在当前目录下看到 a.out。

事实上,从源代码生成可执行文件可以分为四个步骤,分别是预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。下图是 GCC 生成 a.out 的过程:


预处理(Preprocessing)

预处理过程主要是处理那些源文件和头文件中以#开头的命令,比如 #include、#define、#ifdef 等。预处理的规则一般如下:

  • 将所有的#define删除,并展开所有的宏定义。
  • 处理所有条件编译命令,比如 #if、#ifdef、#elif、#else、#endif 等。
  • 处理#include命令,将被包含文件的内容插入到该命令所在的位置,这与复制粘贴的效果一样。注意,这个过程是递归进行的,也就是说被包含的文件可能还会包含其他的文件。
  • 删除所有的注释///* ... */
  • 添加行号和文件名标识,便于在调试和出错时给出具体的代码位置。
  • 保留所有的#pragma命令,因为编译器需要使用它们。

预处理的结果是生成.i文件。.i文件也是包含C语言代码的源文件,只不过所有的宏已经被展开,所有包含的文件已经被插入到当前文件中。当你无法判断宏定义是否正确,或者文件包含是否有效时,可以查看.i文件来确定问题。

在 GCC 中,可以通过下面的命令生成.i文件:

$gcc -E demo.c -o demo.i

-E表示只进行预编译。

在 Visual Studio 中,在当前工程的属性面板中将“预处理到文件”设置为“是”,如下图所示:

然后点击“运行(Run)”或者“构建(Build)”按钮,就能在当前工程目录中看到 demo.i 。

编译(Compilation)

编译就是把预处理完的文件进行一些列的词法分析、语法分析、语义分析以及优化后生成相应的汇编代码文件。编译是整个程序构建的核心部分,也是最复杂的部分之一,涉及到的算法较多,我们并不打算深入讨论,有兴趣的读者请查看《编译原理》。

在 GCC 中,可以使用下面的命令生成.s文件:

$gcc -S demo.i -o demo.s

或者

$gcc -S demo.c -o demo.s

在 Visual Studio 中,不用进行任何设置就可以在工程目录下看到 demo.asm 文件。

汇编(Assembly)

汇编的过程就是将汇编代码转换成可以执行的机器指令。大部分汇编语句对应一条机器指令,有的汇编语句对应多条机器指令,我们在《C语言内存精讲》中的《一个程序在计算机中到底是如何运行的》一节对汇编语言进行了简单的解释。

汇编过程相对于编译来说比较简单,没有复杂的语法,也没有语义,也不需要做指令优化,只是根据汇编语句和机器指令的对照表一一翻译就可以了。

汇编的结果是产生目标文件,在 GCC 下的后缀为.o,在 Visual Studio 下的后缀为.obj

链接(Linking)

目标文件已经是二进制文件,与可执行文件的组织形式类似,只是有些函数和全局变量的地址还未找到,程序不能执行。链接的作用就是找到这些目标地址,将所有的目标文件组织成一个可以执行的二进制文件。

预处理和汇编的过程都比较简单,有了上面的介绍,相信大家很容易理解。

编译的过程最为复杂,可以细分为词法分析、语法分析、语义分析和指令优化,这里涉及到诸多算法以及正则表达式,我们并不打算深入分析,也没必要,有兴趣的读者请自行查阅《编译原理》。

而目标文件的结构、可执行文件的结构、链接的过程是我们要重点研究的,它能够让我们明白多文件编程以及模块化开发的原理,这是大型项目开发的基石。

最后需要说明的是:汇编的过程非常简单,仅仅是查表翻译,我们通常把它作为编译过程的一部分,不再单独提及。这样,源文件经过预处理、编译和链接就生成了可执行文件。

C语言:编译具体过程及隐藏的更多相关文章

  1. c语言编译执行过程

    <h4>认识C编译执行过程</h4>认识C编译执行过程,是C学习的开端.简单说C语言从编码编译到执行要经历一下过程: C源代码编译---->形成目标代码,目标代码是在目标 ...

  2. C语言编译各过程

    1.预处理 此阶段主要完成#符号后面的各项内容到源文件的替换,往往一些莫名其妙的错误都是出现在头文件中的,要在工程中注意积累一些错误知识. (1).#ifdef等内容,完成条件编译内容的替换 (2). ...

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

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

  4. C语言编译过程

    GCC编译C源码有四个步骤: 预处理-----> 编译 ----> 汇编 ----> 链接 一. 编译和链接的流程 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在 ...

  5. c语言编译预处理和条件编译执行过程的理解

    在C语言的程序中可包括各种以符号#开头的编译指令,这些指令称为预处理命令.预处理命令属于C语言编译器,而不是C语言的组成部分.通过预处理命令可扩展C语言程序设计的环境. 一.预处理的工作方式 1.1. ...

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

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

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

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

  8. 转:C语言的编译链接过程的介绍

    11:42:30 C语言的编译链接过程要把我们编写的一个c程序(源代码)转换成可以在硬件上运行的程序(可执行代码),需要进行编译和链接.编译就是把文本形式源代码翻译为机器语言形式的目标文件的过程.链接 ...

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

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

随机推荐

  1. eclipse自动格式化代码

    前段时间在Eclipse里面设置了java文件保存时自动格式化,在java->Code Style->Formatter里设置了自定义的格式化的样式,这样每次保存后都会自动格式化代码,用了 ...

  2. SLAM相机定位

    SLAM相机定位 摘要 深度学习在相机定位方面取得了很好的结果,但是当前的单幅图像定位技术通常会缺乏鲁棒性,从而导致较大的离群值.在某种程度上,这已通过序列的(多图像)或几何约束方法解决,这些方法可以 ...

  3. Action4D:人群和杂物中的在线动作识别:CVPR209论文阅读

    Action4D:人群和杂物中的在线动作识别:CVPR209论文阅读 Action4D: Online Action Recognition in the Crowd and Clutter 论文链接 ...

  4. C ++基本输入/输出

    C ++基本输入/输出 本文将学习如何使用cin对象从用户那里获取输入,并使用cout对象在示例的帮助下向用户显示输出. C ++输出 在C ++中,cout将格式化的输出发送到标准输出设备,例如屏幕 ...

  5. GPU—加速数据科学工作流程

    GPU-加速数据科学工作流程 GPU-ACCELERATE YOUR DATA SCIENCE WORKFLOWS 传统上,数据科学工作流程是缓慢而繁琐的,依赖于cpu来加载.过滤和操作数据,训练和部 ...

  6. 深入理解ES8的新特性SharedArrayBuffer

    简介 ES8引入了SharedArrayBuffer和Atomics,通过共享内存来提升workers之间或者worker和主线程之间的消息传递速度. 本文将会详细的讲解SharedArrayBuff ...

  7. .NET平台系列14 .NET5中的新增功能

    系列目录     [已更新最新开发文章,点击查看详细] .NET5中不包含的内容 尽管 .NET5 框架中提供了一组重要 API,但它并不包括过去20年左右开发的所有 API,但是.NET Stand ...

  8. 692. 前K个高频单词

    2021-05-20 LeetCode每日一题 链接:https://leetcode-cn.com/problems/top-k-frequent-words/ 标签:堆.字典序.哈希表 题目 给一 ...

  9. 【NX二次开发】Block UI 指定方位

    属性说明 属性   类型   描述   常规           BlockID    String    控件ID    Enable    Logical    是否可操作    Group    ...

  10. xxl-job执行器的注册

    一.执行器注册流程 二.具体流程 1.注册监控线程 //类:JobRegistryHelper.java:方法:public void start() registryMonitorThread = ...