一段C++代码的一生(C++代码编译过程详解)

2023-11-09

 用分享的方式成长,用有趣的眼光看世界。 

欢迎来到12 26 25的博客 
热爱编码、算法、知识总结,不定期更新有趣、有料、有营养内容。 让我们共同学习,共同进步。

好文推荐:   从B站(哔哩哔哩)泄露的源码里发现了B站视频推荐的秘密

                     值得学习17个C/C++ 超经典开源项目,面试加分

                   【刷题笔记0】系列目录索引(持续更新 & 推荐收藏) 

目录

写在前面:

正文:

一、预处理阶段(产生.i文件, -E)

 二、编译阶段(产生.s文件,-s)

三、汇编阶段(产生.o或.obj文件, -c)

四、链接阶段(产生.out或.exe文件, -o)

总结

参考资料:


 

写在前面:

每个入门C++的同学都离不开编写 Hallo World,然而书写代码只是第一步(诞生),而后你的Hallo World代码是如何“成长”到机器可以运行的文件?这个也是整个C++程序一生中重要的阶段,也是每个C++程序中的基础问题。

本文就以“Hallo World”为例,讲述一下C++如何从文本到可执行文件的整个详细过程,让我们一起打开编译器的“小黑盒”,用最简明的办法来解释 C++ 编译器到底都对你的代码施了什么“魔法”吧。

正文:

如今底层机器语言难以用于编写足够复杂的现代应用程序,作为一门高级编程语言,C++ 的出现让程序员的编程工作变得更加容易。编译器通过将 C++ 源码转换成计算机可以执行的二进制文件,填补了高级 C++ 语言和机器语言之间的空白。

以Hallo World 为例子,程序:

// helloworld.cpp
#include <cstdio>
int main(){
    printf("hello world!\n");
    return 0;
}

编译过程只需:

$ g++ helloworld.cpp # 编译
$ ./a # 执行
hello world!

编译过程看似容易,只需一个"g++",以至于大家觉得编译事件很简单的事。事实真的如此吗?、

上述gcc命令其实依次执行了四步操作:1.预处理(Preprocessing), 2.编译(Compilation), 3.汇编(Assemble), 4.链接(Linking)。

大致流程如图所示:

预处理阶段:对源代码文件中文件包含关系(头文件)、预编译语句(宏定义)进行分析和替换,生成 预编译文件.i

编译阶段:将经过预处理后的预编译文件转换成特定汇编代码,生成 汇编文件.s

汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成 可重定位目标文件.o

链接阶段:将多个目标文件及所需要的库连接成最终的 可执行目标文件.exe

展开来讲:

一、预处理阶段(产生.i文件, -E)

首先是源代码文件helloworld.cpp和相关头文件预处理成一个.i文件。命令如下

g++ -E helloworld.cpp -o helloworld.i

在实际编译工作开始之前,预处理器指令指示编译器对源码进行临时扩充,以为之后的步骤做好准备。

在 C++ 中,预处理器指令以 # 号开头,比如 #include#define 和 #if 等。在这一阶段,编译器逐个处理 C++ 源码文件。

  • 对于 #define 指令,编译器将源码中的宏替换成宏定义中的内容;
  • 对于 #if#ifdef 和 #ifndef 指令,编译器将有选择地跳过或选中部分源代码;
  • 而对于 #include 指令,编译器将把对应的库的源码插入到当前源代码中——这通常是一些通用的声明。被 #include 指令引入的头文件( .h )往往会包含大量的代码,你引入的越多,最后生成的预编译文件就越大。
  • 预处理过程还会过滤掉所有注释/**/和//里面的内容。

  • 另外还会添加行号和文件名标识,使编译器能分辨出每一行来自哪个文件,以便在调试过程中能生成对应的错误信息

  • 最后会保留#pragma编译器指令,因为编译器需要使用它们。如:#pragma once 是为了防止有文件被重复引用。

总的来说,预编译过的文件会比原来的 C++ 源码更大一些。

问:

1、#ifndef,#ifdef,#endif的作用?

  防止重复包含头文件 

2、#include尖括号和双引号的区别?

  #include<>,从标准库中寻找头文件

  #include " ",从当前目录开始寻找头文件

附:

下表是常用的一些预处理命令

还有下列几种预处理宏(是双下划线)
__LINE__ 表示正在编译的文件的行号
__FILE__表示正在编译的文件的名字__DATE__表示编译时刻的日期字符串,例如: "25 Dec 2007"
__TIME__ 表示编译时刻的时间字符串,例如: "12:30:55"
__STDC__ 判断该文件是不是定义成标准 C 程序

 二、编译阶段(产生.s文件,-s)

编译阶段是检查语法,将去除了预编处理器指令的纯 C++ 代码生成汇编。将预处理的文件进行一系列的词法分析,语法分析,语义分析,以及优化后产生相应的汇编代码文件,这个过程是程序构建的核心部分,也是最复杂的。在 C++ 中,如果一个对象只声明,不进行定义,编译器仍然可以从源代码产生目标文件,因为这个对象也可以指向某些当前代码中还未定义的标识符。

执行命令(-s)如下:

g++ -S helloworld.i -o helloworld.s

词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
优化:源代码级别的一个优化过程。
目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。

在这里插入图片描述

三、汇编阶段(产生.o或.obj文件, -c)

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

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

  • 数据段:主要存放程序中要用到的各种全局变量或静态的数据。一般数据段都是可读,可写,可执行的。

这一步生成的目标文件可以被放在被称为静态库的包中,以备后续使用——也就是说,如果你只修改了一个文件,你并不需要重新编译整个项目的源代码。

四、链接阶段(产生.out或.exe文件, -o)

链接就是把每个源代码独立的编译,然后按照它们的要求将它们组装起来,链接主要解决的是源代码之间的相互依赖问题,链接的过程包括地址和空间的分配,符号决议,和重定位等这些步骤。

在这一阶段,编译器将把上一阶段中编译器产生的各种目标文件链接起来,将未定义标识符的引用全部替换成它们对应的正确地址。没有把目标文件链接起来,就无法生成能够正常工作的程序,就像一页没有页码的目录一样,没什么用处。完成链接工作之后,链接器根据编译目的不同,把链接的结果生成为一个动态链接库,或是一个可执行文件。

链接的过程也会抛出各种异常,通常是重复定义或者缺失定义等错误。不只是没进行定义的情况,如果你忘记将对某个库或是目标文件的引用导入进来,让链接器能找到定义的话,也会发生这类错误。重复定义则刚好相反,当有两个库或目标文件中含有对同一个标识符的定义时,就可能出现重复定义错误。

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

1、静态链接/库

  在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中,因此对应的链接方式称为静态链接。

  静态库可以简单看成是一组目标文件(.o/.obj文件)的集合,即很多目标文件经过压缩打包后形成的一个文件

在这里插入图片描述
  静态库的缺点在于:浪费空间和资源,因为所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件


2、动态链接/库

动态库在程序编译时并不会被连接到目标代码中,而是在程序运行是才被载入不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
在这里插入图片描述

总结

本文中,我们介绍了 C++ 编译过程的各个阶段,更加详细地了解了整个过程。通过学习俗如何使用 C++ 编译器,并对各种 C++ 编译器进行概述,你得以一窥编译过程的幕后细节,并对它有了一些深入的了解,希望能给你带来帮助。

参考资料:

1. GCC and Make - A Tutorial on how to compile, link and build C/C++ applications (ntu.edu.sg)

2. 10 分钟看懂 C++ 编译过程 | 坎德人的小包包 (oicebot.github.io)

3. 一个C++源文件从文本到可执行文件经历的过程_青萍之末的博客-CSDN博客_c++源文件从文本到可执行文件经历的过程


 

上一篇: 从B站 (哔哩哔哩) 泄露的源码里发现了B站视频推荐的秘密

下一篇: 400+条实用C/C++框架、库、工具整理 ,你能想到的都在这里了

如果有什么要补充的,欢迎下方

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

一段C++代码的一生(C++代码编译过程详解) 的相关文章

  • Taglib:性能和崩溃问题

    我在 Qt 应用程序中使用 taglib 库 1 7 2 从音乐文件夹中读取 mp3 文件的一些元数据 问题是我发现它非常慢 例如 这是代码 QString path C Music QDir d path QStringList file
  • 遍历后加快数组查找速度?

    我有一个123MB大的int数组 它基本上是这样使用的 private static int data new int 32487834 static int eval int c int p data c 0 p data p c 1 p
  • 是否可以使静态控件透明?

    我正在尝试实现一个静态控件 该控件刷新 更改文本 以响应每秒发生一次的某个事件 由于我不想每秒绘制整个客户区域 所以我决定使用静态控件 现在的问题是父窗口被蒙皮 这意味着它有自定义位图作为背景 而静态控件没有适应 所以我正在寻找使静态控件的
  • 根据当前文化调用不同(本地化)视图

    我在用着LocalizationAttribute它实现了ActionFilterAttribute本地化视图 我简单地说 Localize 在控制器上 我使用 LocalizeStrings resx 文件根据当前线程上的语言进行应用 一
  • 我们可以在 C# 中定义枚举的隐式转换吗?

    是否可以在 C 中定义枚举的隐式转换 可以实现这一目标的东西吗 public enum MyEnum one 1 two 2 MyEnum number MyEnum one long i number 如果没有 为什么不呢 有一个解决方案
  • 获取当前用户的 NetworkCredential (C#)

    我正在尝试从控制台应用程序调用 Web 服务 并且我需要向客户端提供System Net NetworkCredential object 是否有可能创建一个NetworkCredential启动应用程序的用户的对象而不提示输入用户名 密码
  • OWIN AuthenticationOptions 在 mvc5 应用程序中运行时更新

    Hi 情况如下 我在 iis 7 上有一个带有 Identity 2 的 MVC 5 应用程序 该应用程序为多个网站提供服务 主机名是某些网站的关键 网站 另一个网站 等等 我决定在我的所有网站上使用谷歌外部登录 每个网站都应该是带有个人
  • 带方括号的 Uri.EscapeUriString

    这是一个奇怪的问题 但让我们看看它会得到什么样的回应 如果我编写一个控制台应用程序 VS 2013 NET 4 5 1 并执行这行代码 Uri EscapeUriString 我明白了 但是 如果我执行同样的事情 嗯 从技术上来说Uri E
  • 在异步方法中使用时 HttpClient 标头被清空

    我正在使用 NET Framework 4 6 1 我的 Web api 中有一个控制器 其中有静态 HttpClient 来处理所有 http 请求 在 IIS 上托管我的应用程序后 大约每月一次 我的应用程序的所有传入请求都会出现以下异
  • 外部剃刀视图看不到外部模型

    我对外部剃刀视图有疑问 在我的项目中 我有主 mvc Web 程序集和动态加载的外部类库程序集 来自 DB 及其自己的控制器 视图和模型 这些程序集在运行时不会直接引用和加载 我能够通过为控制器创建自定义控制器工厂 为视图创建自定义虚拟路径
  • 解析通过asp:FileUpload上传的XML文件

    我有一个场景 用户将上传 XML 文件 我想将该文件添加到数据库中的表中 不过 困难的部分是我需要解析文件 然后将一些信息添加到一些不同的表中 显示如何获取 XML 文件的每个示例都使用 URI 来获取文件 但是如何直接从数据库获取文件 或
  • 如何让 PCRE 与 C++ 一起使用?

    这是一个新手问题 但我希望我能尽可能清楚地表达我的问题 我正在尝试用 C 进行模式匹配 我已经从以下位置下载了 PCRE 的 Win32 版本here http gnuwin32 sourceforge net packages pcre
  • 链接错误:xxx 已在 *****.LIB 中定义:: 究竟出了什么问题?

    Problem 我正在尝试使用一个名为DCMTK http dicom offis de dcmtk它使用了一些其他外部库 zlib libtiff libpng libxml2 libiconv 我已经从同一网站下载了这些外部库 LIB
  • 如何通过 Excel 互操作对象自动调整列大小?

    下面是我用来将数据加载到 Excel 工作表中的代码 但我希望在加载数据后自动调整列的大小 有谁知道自动调整列大小的最佳方法 using Microsoft Office Interop public class ExportReport
  • Subsonic 3 ActiveRecord 嵌套选择导致 NotIn 错误?

    我有以下 Subsonic 3 0 查询 其中包含嵌套的 NotIn 查询 public List
  • 是否可以在 Eclipse 中为除 Java 之外的 Eclipse 编写插件?

    谁能帮我用c 写一个eclipse插件 weekens 和 celavek 感谢您提供的信息 我正在研究 JNI 并将尝试实现它 celavek 我们必须做什么样的主控 控制 在C 和java接口中处理是否风险更大 我的要求是在 Java
  • PC 上 XNA 中的信箱和缩放

    有没有一种方法可以让我基本上以 1080p 或 720p 作为默认分辨率来开发 XNA 游戏 然后根据设置的分辨率将游戏中的所有内容缩放到适当的大小 而不必在每个 Sprite 中设置缩放因子Draw 方法 我的想法是 我可以基于 1080
  • 预览MouseMove 与 MouseMove

    我有相当多的 XAML 经验 但最近我注意到我的大多数同事都使用预览鼠标移动代替鼠标移动事件 我一直用鼠标移动它对我很有帮助 但我忍不住问我什么时候应该使用预览鼠标移动什么时候鼠标移动 有什么区别 各自有什么优点和缺点等等 PreviewM
  • 从不同的线程访问对象

    我有一个服务器类 它基本上等待来自客户端的连接 在该类中 我创建了一个 NetworkStream 对象 以便能够从客户端接收字节 由于 NetworkStream Read 方法不是异步的 这意味着它将等到从客户端读取字节才能继续执行类似
  • RC4 实现与 openssl 输出不匹配

    我的目标是在 C C 中实现 RC4 流密码 并确保它产生与使用时相同的输出openssl命令 按照伪代码维基百科 https en wikipedia org wiki RC4 该实现似乎有效 因为它可以加密和解密内容 但是 加密的输出与

随机推荐

Powered by Hwhale