C/C++程序编译步骤以及如何生成可执行文件

2023-05-16

一、开篇

        许久不碰关于这方面的知识了,前几天同学开课提及到该部分,正好作为回顾吧。

        C/C++语言很多人都比较熟悉,这基本上是每位大学生必学的一门编程语言,通常还都是作为程序设计入门语言学的,并且课程大多安排在大一(反正我是混过来的)。刚上大学,学生们还都很乖,学习也比较认真、用心。所以,C/C++语言掌握地也都不错(说的是你么),不用说编译程序,就是写个上几百行的程序都不在话下,但是他们真的知道C/C++程序编译的步骤么?

        很多人都不是很清楚吧,如果接下来学过“编译原理”,也许能说个大概。VC的“舒适”开发环境屏蔽了很多编译的细节,这无疑降低了初学者的入门门槛,但是也“剥夺”了他们“知其所以然”的权利,致使很多东西只能“死记硬背”,遇到相关问题就“丈二”。国内教育,隐匿了关于程序代码变成计算机可执行的语言之间的一切过程,悲剧~~~所以,这部分只能自己查资料了,在此推荐两本书,一个是老外的《深入理解计算机系统》,另外一本就是国人写的非常优秀的一本关于底层介绍的书籍《程序员的自我修养》。

        本篇博客仅作为关于“C/C++程序编译步骤以及如何生成可执行文件”的简要介绍。

三、正文

1、写在前面

        关于学习编程的过程,一是刷各家公司的笔试题,各种奇葩的笔试题,挖了各种坑,这样才能让你快速进步;二是看了liutao的《囫囵C语言》系列,写的太精辟了,幽默的语言以及深入的理解。可以作者很久不更新了。应该是退出“神坛”了吧。

        电子计算机所使用的是由“0”“1”组成的二进制数,二进制是计算机的语言的基础。计算机发明之初,人们只能降贵纡尊,用计算机的语言去命令计算机干这干那,一句话,就是写出一串串由“0”“1”组成的指令序列交由计算机执行,这种语言,就是机器语言。想象一下老前辈们在打孔机面前数着一个一个孔的情景,嘘,小声点,你的惊吓可能使他们错过了一个孔,结果可能是导致一艘飞船飞离轨道。

        为了减轻使用机器语言编程的痛苦,人们进行了一种有益的改进:用一些简洁的英文字母、符号串来替代一个特定的指令的二进制串,比如,用“ADD”代表加法,“MOV”代表数据传递等等,这样一来,人们很容易读懂并理解程序在干什么,纠错及维护都变得方便了,这种程序设计语言就称为汇编语言,即第二代计算机语言。然而计算机是不认识这些符号的,这就需要一个专门的程序,专门负责将这些符号翻译成二进制数的机器语言,这种翻译程序被称为汇编程序。因为汇编指令和机器语言之间有着一一对应的关系,这可比英译汉或汉译英简单多了。

        高级语言是偏向人,按照人的思维方式设计的,机器对这些可是莫名奇妙,不知所谓。鱼与熊掌的故事在计算机语言中发生了。于是必须要有一个桥梁来衔接两者,造桥可不是一件简单的事情。当你越想方便,那桥就得越复杂。那高级语言是如何变成机器语言的呢,这个过程让我慢慢道来。

2、转换过程

        平时大家写代码然后编译即可生产计算机可以执行的指令,其实这个转换过程中有许多重要的过程,下面作详细介绍。     

        编译:将源代码转换为机器可认识代码的过程。编译程序读取源程序(字符流),对之进行词法和语法的分析,将高级语言指令转换为功能等效的汇编代码,再由汇编程序转换为机器语言,并且按照操作系统对可执行文件格式的要求链接生成可执行程序。       

        C源程序->编译预处理->编译程序(生成*.s文件)>优化程序->汇编程序(生成*.o文件)>链接程序->可执行文件(*.out

3、细化

3.1、编译预处理

        编译预处理:读取c源程序,对其中的伪指令(以#开头的指令)和特殊符号进行处理。伪指令主要包括以下四个方面:

        (1)宏定义指令,如# define Name TokenString,#undef等。对于前一个伪指令,预编译所要作得的是将程序中的所有NameTokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。

        (2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉。

        (3)头文件包含指令,#include "FileName"或者#include<FileName>等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的主要目的是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号(<>系统头文件)。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号(""自定义头文件)。

        (4)特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。预编译程序所完成的基本上是对源程序的替代工作。经过此种替代,生成一个没有宏定义、没有条件编译指令、没有特殊符号的输出文件。这个文件的含义同没有经过预处理的源文件是相同的,但内容有所不同。下一步,此输出文件将作为编译程序的输入而被翻译成为机器指令。删除所有注释“//”“/*   */”以及添加行号,便于编译器编译时产生调试用的行号信息及用于编译时产生编译错误或警告时显示行号。

3.2、 编译阶段

        经过预编译得到的输出文件中,只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main、if、else、for、while、{,}、+、-、*、\等等。编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码(符号表)

3.3、优化阶段

        优化处理是编译系统中一项比较艰深的技术。它涉及到的问题不仅同编译技术本身有关,而且同机器的硬件环境也有很大的关系。优化一部分是对中间代码的优化。这种优化不依赖于具体的计算机。另一种优化则主要针对目标代码的生成而进行的。上图中,我们将优化阶段放在编译程序的后面,这是一种比较笼统的表示。

        对于前一种优化,主要的工作是删除公共表达式、循环优化(代码外提、强度削弱、变换循环控制条件、已知量的合并等)、复写传播,以及无用赋值的删除等等。后一种类型的优化同机器的硬件结构密切相关,最主要的是考虑如何充分利用机器的各个硬件寄存器存放的有关变量的值,以减少对于内存的访问次数。另外,如何根据机器硬件执行指令的特点(如流线、RISCCISCVLIW等)而对指令进行一些调整使目标代码比较短,执行的效率比较高,也是一个重要的研究课题。经过优化得到的汇编代码必须经过汇编程序的汇编转换成相应的机器指令,方可能被机器执行。

3.4、 汇编过程

        汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个汇编源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。目标文件由段组成。通常一个目标文件中至少有两个段:

            代码段: 该段中所包含的主要是程序的指令。该段一般是可读和可执行的,但一般却不可写。 

            数据段: 主要存放程序中要用到的各种全局变量或静态的局部变量。.rodata .data

        UNIX环境下主要有三种类型的目标文件:

        (1可重定位文件: 其中包含有适合于其它目标文件链接来创建一个可执行的或者共享的目标文件的代码和数据。

        (2共享的目标文件: 这种文件存放了适合于在两种上下文里链接的代码和数据。第一种是链接程序可把它与其它可重定位文件及共享的目标文件一起处理来创建另一个目标文件;第二种是动态链接程序将它与另一个可执行文件及其它的共享目标文件结合到一起,创建一个进程映象。

        (3可执行文件: 它包含了一个可以被操作系统创建一个进程来执行之的文件。汇编程序生成的实际上是第一种类型的目标文件。对于后两种还需要其他的一些处理方能得到,这个就是链接程序的工作了。

3.5、 链接程序

        由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。

        链接程序的主要工作就是将有关的目标文件彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体。

        根据开发人员指定的库函数的链接方式的不同,链接处理可分为两种:

        (1)静态链接  在这种链接方式下,函数的代码(被应用程序引用的目标模块)将从其所在地静态链接库中被拷贝到最终的可执行程序中。这样该程序在被执行时这些代码将被装入到该进程的虚拟地址空间中。静态链接库实际上是一个目标文件的集合,其中的每个文件含有库中的一个或者一组相关函数的代码。静态连接的劣势:浪费内存和磁盘空间,模块更新困难。

        (2)动态链接  在此种方式下,函数的代码被放到称作是动态链接库或共享对象的某个目标文件中。链接程序此时所作的只是在最终的可执行程序中记录下共享对象的名字以及其它少量的登记信息。在此可执行文件被执行时,动态链接库的全部内容将被映射(优点:无拷贝环节,在内存中只有一份此共享代码,以节约存储器空间)到运行时相应进程的虚地址空间。动态链接程序将根据可执行程序中记录的信息找到相应的函数代码。

        动态连接解决了共享的目标文件多个副本浪费磁盘和内存空间的问题。在内存中共享一个目标文件模块的好处不仅仅是节省内存,还可以减少物理页面的换入换出,亦可以增加CPU的cache hit (关于这部分在《深入理解计算机系统》中有详细介绍,尤其是程序的局部性原理的应用,以前写代码都是瞎写,根本不知道还有这么个优势)。

        动态连接也有其缺点:很常见的一个问题是,当程序所依赖的某个模块更新后,由于新的模块与旧的模块之间接口不兼容,导致原有的程序无法运行。

        下面是一些对静态库的介绍,帮助理解。

        A static library is like abookstore, and a shared library is like... a library. With the former, you getyour own copy of the book/function to take home; with the latter you andeveryone else go to the library to use the same book/function. So anyone who wantsto use the (shared) library needs to know where it is, because you have to"go get" the book/function. With a static library, the book/functionis yours to own, and you keep it within your home/program, and once you have ityou don't care where or when you got it.

        The advantages of static libraries is thatthere are no dependancies required for the user running the application - e.g.they don't have to upgrade their DLL of whatever... The disadvantages is thatyour application is larger in size because you are shipping it with all the librariesit needs.

        Sharedlibraries are .so (or in Windows .dll, or in OS X .dylib) files. All the coderelating to the library is in this file, and it is referenced by programs usingit at run-time. A program using a shared library only makes reference to thecode that it uses in the shared library.

        Staticlibraries are .a (or in Windows .lib) files. All the code relating to thelibrary is in this file, and it is directly linked into the program at compiletime. A program using a static library takes copies of the code that it usesfrom the static library and makes it part of the program. [Windows also has.lib files which are used to reference .dll files, but they act the same way asthe first one].

        Sharedlibraries reduce the amount of code that is duplicated in each program thatmakes use of the library, keeping the binaries small. It also allows you toreplace the shared object with one that is functionally equivalent, but mayhave added performance benefits without needing to recompile the program thatmakes use of it. Shared libraries will, however have a small additional costfor the execution of the functions as well as a run-time loading cost as allthe symbols in the library need to be connected to the things they use.Additionally, shared libraries can be loaded into an application at run-time,which is the general mechanism for implementing binary plug-in systems.

        Staticlibraries increase the overall size of the binary, but it means that you don'tneed to carry along a copy of the library that is being used. As the code isconnected at compile time there are not any additional run-time loading costs.The code is simply there.

Personally,I prefer shared libraries, but use static libraries when needing to ensure thatthe binary does not have many external dependencies that may be difficult tomeet, such as specific versions of the C++ standard library or specificversions of the Boost C++ library.

        What some people have failed to mention is thatwith static libraries the compiler knows which functions your application needsand can then optimize it by only including those functions. This can cut downon library size massively, especially if you only use a really small subset ofa really large library!

4、可执行文件

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

        经过上述五个过程,C源程序就最终被转换成可执行文件了

四、总结

        看了那么多,是不是有了一个全新的认识呢?那么考察一下成果吧,下面是一个小测试。

File: hw.c
#include <stdio.h>
int main(int argc, char *argv[])
{
        printf("Hello World!\n");
        return 0;//小检测:该语句可以省略吗?Why?
}
        为什么一个编译好的简单的Hello World程序也需要占据好几KB的内存空间呢?
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C/C++程序编译步骤以及如何生成可执行文件 的相关文章

随机推荐

  • 真香!腾讯竟然又偷偷开源了一套Android原生UI框架!

    今天在浏览技术新闻的时候 xff0c 发现腾讯就在今天开源了一套 Android 原生的 UI 框架 你们有没有发现 xff0c 腾讯特别喜欢干这种事 xff0c 哪一种事呢 xff1f 喜欢开源 UI 框架 xff0c 小程序也是这样的
  • STM32 Keil5报错-Target uses ARM-Compiler ‘Default Compiler Version 5‘ which is not available-安装编译器解决

    现象 Keil MDK 5 3 6 编译代码报错 xff1a Target uses ARM Compiler Default Compiler Version 5 which is not available Rebuild starte
  • prometheus编译安装

    prometheus是搜集应用程序所使用的CPU 内存 以及磁盘使用大小的神器 xff0c 它可以根据用户所配置的性能阀值给出相应的处理 xff0c 比如 邮件告警等 xff0c 在这里就不啰嗦了 xff0c 由于本人也是刚刚学习 所以能说
  • 不同硬件传感器数据之间的时间同步问题

    主要分为硬同步和软同步 硬同步就是用一个硬件触发器 xff0c 直接通过物理信号 xff0c 触发相机和LIDAR记录一个data frame 软同步提供一个相同的时间源 xff08 一般都是主控电脑utc时间 xff09 给相机和LIDA
  • 多传感器融合定位开源工程与论文

    目录 1 LIC Fusion xff1a 基于激光雷达 惯性导航和相机结合的里程计 2 使用点线特征配合激光雷达辅助的单目视觉里程计 3 间歇的GPS辅助VIO xff1a 在线初始化和标定 4 强大的高精度视觉惯性激光SLAM系统 5
  • [BLE]CC2640之定时器(Clock)事件

    一 定时器 xff08 Clock xff09 所谓定时器本质上递减计数器 xff0c 当计数器减到零时可以触发某种动作的执行 这种动作可以通过回调函数来实现 xff0c 当定时器计时完成后 xff0c 自定义的回调函数会立即被调用 回调函
  • [BLE]低功耗蓝牙介绍

    一 BLE的协议栈框架 BLE协议栈包括两个部分 xff0c 主机 Host 和控制器 Controller 二者通过HCI Host Controller Interface 标准接口相互通信 常用的单芯片单模BLE芯片有TI的CC254
  • [BLE]低功耗蓝牙之GAP、GATT

    一 开篇 本篇主要介绍一下关于BLE开发过程中必须了解的两个协议 xff1a GAP xff08 通用访问协议 xff09 GATT xff08 通用属性协议 xff09 两个协议都隶属于Host层 xff0c 直接关系到应用层开发 xff
  • [memory]虚拟地址空间分布

    一 开篇 踏入嵌入式软件行业也接近2年了 xff0c 从研一开学起懵懵懂懂的开始学习C语言 xff0c 由于本科时对这方面了解的少之又少 xff0c 所以学起来比较困难 xff0c 但是有一群无私奉献的小伙伴 xff0c 慢慢的 xff0c
  • Pixhawk之UAV控制理论、ardupilot源码框架介绍

    一 开篇 您有无人机么 xff1f 没有 那赶紧去某宝买一套 昨天 开会开到接近下午一点钟 xff0c 收获相当大 xff0c 原本不太清楚的ardupilot框架现在也大致熟悉了 xff0c 接下来主要就是结合源码了解其控制过程了 xff
  • RT-Thread学习笔记(11):互斥量

    目录 互斥量的优先级继承机制互斥量和二值信号量的区别 互斥量的运作机制互斥量控制块 互斥量的优先级继承机制 在RT Thread操作系统中为了降低优先级翻转问题利用了优先级继承算法 优先级继承算法是指 xff0c 暂时提高某个占有某种资源的
  • [BLE]CC2640之ADC功能实现和供电电压的采集

    一 开篇 Write programs that do one thing and do it well 发现很多人关于使用CC2640 CC2650的过程中比较难以应对的问题就是实现ADC xff0c 为了方便大家 xff0c 所以有了本
  • [Index]博文索引

    为了方便查看需要的博文 xff0c 在此给出所有博文的索引链接地址 UAV Software Version xff1a ArduCopter xff08 Ver 3 3 xff09 Hardware Version xff1a pixha
  • Pixhawk之姿态解算篇(1)_入门篇(DCM Nomalize)

    一 开篇 慢慢的 慢慢的 慢慢的就快要到飞控的主要部分了 xff0c 飞控飞控就是所谓的飞行控制呗 xff0c 一个是姿态解算一个是姿态控制 xff0c 解算是解算 xff0c 控制是控制 xff0c 各自负责各自的任务 xff0c 我也不
  • Pixhawk之姿态解算篇(4)_补充篇

    一 开篇 大家期待已久的第四篇来了 xff0c 但是本篇可能比较水啊 见谅 首先 xff0c 上一周没有什么收获 xff0c 虽然看了不少的论文 xff0c 但是却没有什么质的飞越 看的论文都是关于姿态解算的 xff0c 用的算法大部分也都
  • Pixhawk之学习杂谈

    一 开篇 距离上一篇博文已经很久了 xff0c 最近主要就是参加了几家公司的电话面试 xff0c 思考了一些问题 xff0c 本身就是半路杀进无人机领域的门外汉 对整个飞行控制部分理解的也是皮毛的皮毛 xff0c 经过几家面试之后 xff0
  • Pixhawk之姿态控制篇(2)_控制策略

    一 开篇 写在前面 xff1a 先占坑 现在关于该部分内部还在完善 xff0c 后续不上 三 实验平台 Software Version xff1a PX4Firmware Hardware Version xff1a pixhawk ID
  • Pixhawk之姿态解算篇(5)_ECF/EKF/GD介绍

    一 开篇 很久没更新blog了 xff0c 最近研究的东西比较杂乱 xff0c 也整理了很多东西 xff0c 没有来的及更新 xff0c 最近发现很多小伙伴都开始写blog了 xff0c 在不更新就要 被落后了 兄弟们 xff0c 等等我啊
  • Pixhawk之姿态解算篇(6)_Gradient Descent

    一 开篇 在多旋翼进行姿态估计的过程中 xff0c 最简单的就是直接使用gyro测量角速度进行积分求取欧拉角 xff08 RPY xff09 xff0c 但是由于gyro自身存在的bias和drift xff0c 导致直接测量过程随着时间的
  • C/C++程序编译步骤以及如何生成可执行文件

    一 开篇 许久不碰关于这方面的知识了 xff0c 前几天同学开课提及到该部分 xff0c 正好作为回顾吧 C C 43 43 语言很多人都比较熟悉 xff0c 这基本上是每位大学生必学的一门编程语言 xff0c 通常还都是作为程序设计入门语