C&C++ 编译过程

视频推荐: 【c/cpp程序编译过程】https://www.bilibili.com/video/BV1JM4m127y7?vd_source=5a0790755035f26a67935abfbfcdfd5b

文章推荐: 详解C/C++代码的预处理、编译、汇编、链接全过程 - 知乎 (zhihu.com) C++ 预编译,编译,汇编,链接 - Suarezz - 博客园 (cnblogs.com) C/C++编译链接 - 知乎 (zhihu.com)(进阶)

关于编译器与不同系统的文件

一般编译器可以分为前端和后端,前端主要负责 语义分析,后端主要负责 代码生成

Linux macOS Windows
可执行文件 .out或/ .out或/ .exe
目标文件 .o .o .obj
静态库 .a .a .lib
动态库 .so .dylib .dll

GCC负责编译,生成各类库和目标文件,然后调用外部链接器进行链接然后生成可执行文件

LLVM同时有编译器和链接器(例如:armclangarmlink

运行程序的运行阶段,是让加载器将最后生成的可执行文件放到内存中

GCC、GNU、gcc与g++

GNU:一个操作系统,具体内容不重要,感兴趣可以参考:

GCC、GNU到底啥意思?_一只杨阳羊的博客-CSDN博客​blog.csdn.net/qq_43617936/article/details/104504992

  • GCC:GNU Compiler Collection(GNU编译器集合)的缩写,可以理解为一组GNU操作系统中的编译器集合,可以用于编译C、C++、Java、Go、Fortan、Pascal、Objective-C等语言
  • gcc:GCC(编译器集合)中的GNU C Compiler(C 编译器)
  • g++:GCC(编译器集合)中的GNU C++ Compiler(C++ 编译器)

简单来说,gcc调用了GCC中的C Compiler,而g++调用了GCC中的C++ Compiler 对于 *.c 和 *.cpp 文件,gcc分别当作 c 和 cpp文件编译,而g++则统一当作cpp文件编译

![[Pasted image 20241126223616.png]]

GDB(gdb)

GDB(gdb)全称“GNU symbolic debugger”,是 Linux 下常用的程序调试器。 为了能够使用 gdb 调试,需要在代码编译的时候加上-g,如

1
g++ -g -o test test.cpp

常用指令

![[Pasted image 20241027090624.png]]

编译过程

![[Pasted image 20241027085312.png]]

![[Pasted image 20241027092346.png]]

step1. 预处理

![[Pasted image 20241026230159.png]] 命令:

1
g++ -E test.cpp -o test.i

作用:

  1. 去掉注释
  2. 预处理指令替换(ifndef ,宏定义等)
  3. include 导入的头文件替换

预处理实例对比

.cpp 文件 ![[Pasted image 20241026230758.png]]

预处理后的 .i 文件

还有替换的

1
#include<iostream>

被省略了,替换的内容有3万多行

翻到最下面查看

![[Pasted image 20241026230952.png]]

具体解释 预处理,顾名思义就是编译前的一些准备工作

预编译把一些#define的宏定义完成文本替换,然后将#include的文件里的内容复制到.cpp文件里,如果.h文件里还有.h文件,就递归展开。在预处理这一步,代码注释直接被忽略,不会进入到后续的处理中,所以注释在程序中不会执行

step2. 编译阶段

![[Pasted image 20241027001928.png]]

命令:

1
g++ -S test.i -o test.s

对 .i 文件进行编译 也可以对 .cpp 文件进行编译

1
g++ -S test.cpp -o test.s

作用:

  1. 翻译成汇编语言
  2. 检查代码报错

具体解释 编译只是把我们写的代码转为汇编代码,它的工作是检查词法和语法规则,所以,如果程序没有词法或则语法错误,那么不管逻辑是怎样错误的,都不会报错
编译不是指程序从源文件到二进制程序的全部过程,而是指将经过预处理之后的程序转换成特定汇编代码(assembly code)的过程

step3. 汇编阶段

![[Pasted image 20241027002224.png]] 命令:

1
g++ -c test.s -o test.o

作用:

  1. 将 .s 文件,汇编成二进制文件(二进制文件不可执行)

step4. 链接阶段

![[Pasted image 20241027002618.png]]

命令:

1
gcc test.o -o test

作用:

  1. 将目标文件 .o和库文件链接

具体解释:C语言代码经过编译以后,并没有生成最终的可执行文件(.exe 文件),而是生成了一种叫做目标文件(Object File)的中间文件(或者说临时文件)。目标文件也是二进制形式的,它和可执行文件的格式是一样的。对于 Visual C++,目标文件的后缀是.obj;对于 GCC,目标文件的后缀是.o。这就是一开始所说的编译完一堆.obj和.o文件的来源。

目标文件经过链接(Link)以后才能变成可执行文件。既然目标文件和可执行文件的格式是一样的,为什么还要再链接一次呢,因为编译只是将我们自己写的代码变成了二进制形式,它还需要和系统组件(比如标准库、动态链接库等)结合起来,这些组件都是程序运行所必须的。链接(Link)其实就是一个“打包”的过程,它将所有二进制形式的目标文件和系统组件组合成一个可执行文件。完成链接的过程也需要一个特殊的软件,叫做链接器(Linker)。

C++程序编译的时候其实只识别.cpp文件。每个cpp文件都会分别编译一次,生成一个.o或者.obj文件。这个时候,链接器除了将目标文件和系统组件组合起来,还需要将编译器生成的多个.o或者.obj文件组合起来。

g++自动链接了系统组件,我们只需要把自定义函数的目标文件与main.o链接即可

链接的其他过程

C/C++编译链接 - 知乎 (zhihu.com)(进阶)

合并段 ![[Pasted image 20241027092742.png]]

调整段偏移 ![[Pasted image 20241027092757.png]]

多文件g++编译指令

![[Pasted image 20241027222011.png]]

1
g++ src/cmake_leran.cpp tools/hello.cpp -I includes -o cmake_leran

src/cmake_leran.cpp tools/hello.cpp 是在指定当前目录下哪些源文件需要编译 -I includes 是 库文件所在的目录

![[Pasted image 20241126230718.png]]

杂谈

关于库文件

![[Pasted image 20241126224636.png]]

![[Pasted image 20241126224803.png]]

关于可执行文件

![[Pasted image 20241126225616.png]]

关于C与C++联合生成可执行文件

在C++中,告诉编译器以C的标准进行编译 C/C++中的 extern 和extern“C“关键字的理解和使用(对比两者的异同)_c extern c-CSDN博客

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
//关键字
extern "C"

//example
#include<iostream>
using namespace std;

extern "C" void func() { //用 extern"C"修饰
}
extern "C" void func(int v) {//用 extern"C"修饰
}

int main() {	
	return 0;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//如果当前文件为cpp文件,就会告诉编译器按C语言方式编译和链接
//如果不是cpp文件,则会被略过

#ifdef __cplusplus
extern "C"
{
#endif

<被调用内容>
#ifdef __cplusplus
}
#enddif

如果编译器是 C++ 编译器,__cplusplus 宏会被定义,extern “C” 块将会被包含,这告诉编译器按照 C 语言的规则来处理函数名称,而不是 C++ 的规则。这样做的目的是为了保持与已经存在的 C 代码库的兼容性,特别是当使用 C++ 编写新代码时。

__cplusplus宏定义作用

在 C++ 中,__cplusplus 是一个预定义的宏,它用于确定正在使用的 C++ 标准的版本。这个宏的存在使得 C++ 代码能够以标准 C 形式输出,即以 C 的形式被调用,这对于跨平台编程和与 C 语言的兼容性至关重要。

宏的定义和使用

__cplusplus 宏通常在编写涉及 C 和 C++ 混合编程的头文件时使用。当 C++ 代码需要被 C 编译器调用时,使用这个宏可以确保 C++ 中的名称修饰(name mangling)不会发生,从而允许 C 代码安全地调用 C++ 函数。例如:

1
2
3
4
5
6
7
8
9
#ifdef __cplusplus
extern "C" {
#endif

// C++ 函数声明

#ifdef __cplusplus
}
#endif

![[Pasted image 20241213212614.png]]

在这个例子中,如果编译器是 C++ 编译器,__cplusplus 宏会被定义,extern “C” 块将会被包含,这告诉编译器按照 C 语言的规则来处理函数名称,而不是 C++ 的规则。这样做的目的是为了保持与已经存在的 C 代码库的兼容性,特别是当使用 C++ 编写新代码时。

宏的值

__cplusplus 宏的值表示 C++ 标准的版本,例如:

  • C++98 标准:199711L
  • C++11 标准:201103L
  • C++14 标准:201402L
  • C++17 标准:201703L
  • C++20 标准:202002L 不同的编译器,如 GCC、Clang 或 Visual C++,都遵循这些标准,并在编译时定义相应的 __cplusplus 值。这个宏的值可以用来确定编译器支持的 C++ 版本,从而在编写代码时做出相应的兼容性处理。
实际应用

在实际应用中,__cplusplus 宏确保了 C++ 代码可以与 C 代码无缝集成。例如,如果有一个用 C 语言编写的库,它的头文件是 f.h,产生的库文件是 f.lib,那么在 C++ 中使用这个库文件时,需要这样写:

1
2
3
extern "C" {
#include "f.h"
}

这样,C++ 编译器就会知道如何正确地链接和调用 C 语言编写的函数。如果没有使用 __cplusplus 宏,可能会出现链接错误,因为 C++ 编译器和 C 编译器对函数名称的处理方式不同。

总结来说,__cplusplus 宏是 C++ 语言为了兼容 C 语言和支持混合编程而提供的一个重要工具。它允许开发者在保持 C++ 功能的同时,确保代码能够在不同的编程环境中正确运行。

关于extern关键字

C/C++中的 extern 和extern“C“关键字的理解和使用(对比两者的异同)_c extern c-CSDN博客 ![[Pasted image 20241213212619.png]]

experience
使用 Hugo 构建
主题 StackJimmy 设计