IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ 程序编译过程:从代码到程序 -> 正文阅读

[C++知识库]C++ 程序编译过程:从代码到程序

????????在大学课堂上学习 C++ 时,老师并没有过多涉猎 C++ 语法背后的知识。也就是说,初学 C++ 时,哪怕写出了代码,我也并不知道从代码到程序的过程中究竟发生了什么。我也曾尝试了解,但作为初学者,面对一堆晦涩难懂的词汇,我也只是看了几眼便放弃了。但知道背后的过程无疑是非常重要的。今日,谨以此文回答我自己学习过程中遇到的问题:C++ 代码是如何变成程序的。


初探 C++ 编译过程

????????C++ 程序编译过程一般可以分为三个步骤:

  1. 预处理 Pre-Processing
  2. 编译 Complication
  3. 链接 Linkinig

????????其中第二步“编译”的意思并不是指从代码到可执行文件的全过程,而是从 C++ 到 machine code 的过程。我们先对这三个步骤进行初步了解:

预处理

? ? ? ? 预处理器 Pre-Processor 接收 C++ 源码文件,处理其中的预处理指令。然后输出一个预处理完毕的文件,文件后缀通常为 i。

编译

? ? ? ? 编译器 Compiler 获取预处理器的输出,将其翻译成汇编语言,汇编语言文件通常以 s 作为后缀。然后再将得到的汇编语言文件翻译成 machine code,得到 object file,也就是目标文件,通常以 o 或者 obj 作为后缀,是二进制文件。

链接

? ? ? ? 链接器 Linker 获取编译器生成的所有目标文件,然后链接程序所需的外部库文件,最终得到一个可执行文件或者库文件。

? ? ? ? 值得注意的是,编译阶段实际上可以再分为从 C++ 到汇编,从汇编到 machine code 两步。因此我们最终细分得到的 C++ 程序编译过程如下:

  1. 预处理 Pre-Processing
  2. 编译 Complication
  3. 汇编 Assemble
  4. 链接 Linkinig

? ? ? ? 该过程如图:

? ? ? ? 接下来,让我们更详细地了解以上四个阶段。


预处理

? ? ? ? 预处理阶段获取源文件处理其中的预处理指令,常见的预处理指令有:

#include // 包含文件
#ifdef   // 如果定义了 xxx
#ifndef  // 如果没有定义 xxx
#define  // 定义宏
#undef   // 移除原先定义的宏
#endif   // 结束 if 判断

? ? ? ? 预处理指令均以“#”开头,更多预处理指令请参考:

Preprocessor directives | Microsoft Docshttps://docs.microsoft.com/en-us/cpp/preprocessor/preprocessor-directives?view=msvc-170&viewFallbackFrom=vs-2019? ? ? ? 在这一阶段,预处理器会根据预处理指令执行相应的操作。例如,#include 指令会让指定文件被包含进预处理文件。宏也会在这一阶段被替换和展开。预处理器处理完所有预处理指令后,生成一个预处理完毕的文件


编译

? ? ? ? 这一阶段主要获取预处理阶段生成的文件,然后将其翻译为汇编代码,得到汇编代码文件。通常来说,我们收到的编译器警告和错误都来自这一阶段。这些警告和错误是由语法错误导致的。


汇编

? ? ? ? 这一阶段会获取编译阶段生成的文件,将其进一步翻译为 machine code,得到 object file,也就是目标文件,目标文件本身是二进制文件。此外,如果你一次编译了多个独立文件,那么每一个文件都会生成对应的目标文件。


链接

? ? ? ? 链接阶段获取汇编阶段生成的目标文件,将所有目标文件编译成可执行文件库文件。然后链接器解析依赖项,链接外部静态库,得到最终的可执行文件库文件。这一阶段最常见的错误是缺少定义或者重复定义,前者由定义不存在或目标文件未给出链接器导致,后者由同一符号在多个目标文件或库文件中重复定义导致。


示例代码

? ? ? ? 接下来,我们以 GCC 为例,通过示例,生成各个阶段对应的文件,对编译的过程进行更详细的了解。示例包含三个文件:math.hpp,math.cpp,main.cpp,你可以在任意位置生成它们。

????????math.hpp:

#ifndef MATH_HPP
#define MATH_HPP

/* Test Comment */
int add (int lhs, int rhs);

#endif

? ? ? ? math.cpp:

#include "math.hpp"

int add (int lhs, int rhs)
{
    return lhs + rhs;
}

? ? ? ? main.cpp:

#include "math.hpp"
#define a 10
#define b 20

int main()
{
    int c = add(a, b);
    return 0;
}

? ? ? ? 文件结构如图,math.cpp 文件和 math.hpp 文件位于 include 文件夹内:


预处理文件

? ? ? ? 打开命令行,定位到文件所在目录,以我的电脑为例,文件目录为。

?D:\Workflow\WorkSpace\QuickStart\Cpp

? ? ? ? 我们使用如下指令对 main.cpp 文件进行预处理:

g++ -E -Iinclude main.cpp -o main.i

? ? ? ? 上面这条指令中各参数作用如下:

-E        告诉编译器执行完预处理就退出
-Iinclude 指定文件夹 include 为头文件目录,此处使用 -I./include 同理
-o        指定输出文件名

? ? ? ? 进行预处理之后会得到预处理文件 main.i,位于 main.cpp 文件同级目录:

? ? ? ? 打开 main.i 文件,其中内容如下,(为了便于阅读,我删除了多余空行):

# 1 "main.cpp"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "main.cpp"
# 1 "include/math.hpp" 1

int add (int lhs, int rhs);
# 2 "main.cpp" 2

int main()
{
    int c = add(10, 20);
    return 0;
}

? ? ? ? 我们重点关注第七行和第十二行。第七行,预处理器在检测到 #include "math.hpp" 后,将 math.hpp 文件的文件体替换到了 main.cpp 文件内。第十二行,预处理器在检测到了宏 a 和

b 时,将其文本替换为 10 和 20。因此我们可以发现,宏会在预处理阶段被处理,宏变量会被替换,宏函数也会进行相应文本替换。此外,我们发现 math.hpp 中的注释也被删除了。


汇编代码文件

? ? ? ? 预处理的下一阶段是编译阶段,为了探究生成的汇编代码,我们使用如下指令编译文件:

g++ -S -Iinclude main.cpp -o main.s

?? ? ? ?上面这条指令中各参数作用如下:

-S        告诉编译器执行完编译就退出
-Iinclude 指定文件夹 include 为头文件目录,此处使用 -I./include 同理
-o        指定输出文件名

? ? ? ? 进行编译阶段后,我们会得到文件 main.s:

? ? ? ? 打开 main.s 文件,内容如下:

	.file	"main.cpp"
	.text
	.def	__main;	.scl	2;	.type	32;	.endef
	.globl	main
	.def	main;	.scl	2;	.type	32;	.endef
	.seh_proc	main
main:
.LFB0:
	pushq	%rbp
	.seh_pushreg	%rbp
	movq	%rsp, %rbp
	.seh_setframe	%rbp, 0
	subq	$48, %rsp
	.seh_stackalloc	48
	.seh_endprologue
	call	__main
	movl	$20, %edx
	movl	$10, %ecx
	call	_Z3addii
	movl	%eax, -4(%rbp)
	movl	$0, %eax
	addq	$48, %rsp
	popq	%rbp
	ret
	.seh_endproc
	.ident	"GCC: (x86_64-posix-seh-rev0, Built by MinGW-W64 project) 8.1.0"
	.def	_Z3addii;	.scl	2;	.type	32;	.endef

? ? ? ? 这一步得到的是汇编代码,你可以看到我是在 Windows 环境下编译的。


目标文件

? ? ? ? 汇编阶段,我们利用如下指令得到目标文件 main.o:

g++ -c main.s -o main.o

??? ? ? 上面这条指令中各参数作用如下:

-c        告诉编译器执行到汇编操作退出
-o        指定输出文件名

? ? ? ? 执行这一步的前提是已经生成了汇编代码文件。此时我们得到了 main.o 文件:

? ? ? ? ?main.o 二进制文件,文本编辑器中只能看到乱码:


链接

? ? ? ? 我们输入如下指令尝试输出可执行文件:

g++ main.o -o main

? ? ? ? 很遗憾,报错了:

main.o:main.cpp:(.text+0x18): undefined reference to `add(int, int)'
collect2.exe: error: ld returned 1 exit status

? ? ? ? 其原因是比较显然的,链接这一步是链接目标文件外部库的。我们在上述的步骤中,自始至终只对 main.cpp 文件进行了编译,而其依赖项 math.hpp 并不是一个外部库文件,我们需要对其进行额外操作。由于 math.hpp 内部函数的实现在 math.cpp 中,我们需要编译?math.cpp 文件,把它转换成一个目标文件,然后链接所有目标文件。

? ? ? ? 我们使用如下指令编译 math.cpp 文件得到目标文件 math.o:

g++ -c ./include/math.cpp -o math.o

? ? ? ? 链接所有目标文件得到可执行文件:

g++ main.o math.o -o main.exe

? ? ? ? 这样,我们就得到了最终的可执行文件 main.exe:

? ? ? ? 上面给出的编译过程复杂且文件繁多,一般来说,我们编译示例中的代码不需要那么复杂,只需要如下一条指令即可:

g++ -Iinclude main.cpp ./include/math.cpp -o main.exe

总结

? ? ? ? 通过以上示例,我们可以了解到,C++ 代码编译过程中,会依次生成

  1. 预处理文件 .i
  2. 汇编代码文件 .s
  3. 目标文件 .o/.obj
  4. 可执行文件 .exe/.out 或库文件 .a/.lib

? ? ? ? ?C++ 代码的编译过程不可谓简单,但是了解编译背后的过程和机理可以很好地帮助我们理解、程序,正如当我知道了宏定义是在预处理阶段展开时的恍然大悟。宏定义的这一特征意味着宏函数不像普通函数有对函数栈空间的压栈操作和内存需求,效率上有一定提升。

? ? ? ? 总之,了解 C++ 编译过程必然是一次充满收获的探险。本文仅概括了 C++ 编译生成可执行文件的过程,并未涉及静态库生成,如您有类似需求请移步:C++静态库与动态库 - 吴秦 - 博客园https://www.cnblogs.com/skynet/p/3372855.html


参考

  1. Learn How to Compile a C++ Program | gamedevunboxedhttps://gamedevunboxed.com/learn-how-to-compile-a-c-program/
  2. How does the compilation/linking process work? | StackOverflowhttps://stackoverflow.com/questions/6264249/how-does-the-compilation-linking-process-work
  3. C语言编译过程详解 | 博客园C语言程序从源代码到二进制行程序都经历了那些过程?本文以Linux下C语言的编译过程为例,讲解C语言程序的编译过程。https://www.cnblogs.com/CarpenterLee/p/5994681.html#top
  4. C++到底是怎么编译的? | 知乎g++编译可执行文件: g++ main.cpp -o main.exe 应该有不少人都好奇过:“g++编译器帮我们做了什么?怎么就让C++的代码变成一个可以完成对应代码指令的可执行二进制文件了?”,这里我们就稍微打破一点砂锅,问多…https://zhuanlan.zhihu.com/p/365820917
  5. C语言编译和链接详解我们平时所说的程序,是指双击后就可以直接运行的程序,这样的程序被称为 可执行程序(Executable Program) 。在 Windows 下,可执行程序的后缀有 .exe 和 .com (其中 .exe 比较常见);在类http://c.biancheng.net/view/1736.html
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-04 13:14:05  更:2022-01-04 13:15:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:56:38-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码