Skip to content

Commit

Permalink
update
Browse files Browse the repository at this point in the history
  • Loading branch information
Qianhongbo committed Dec 6, 2022
1 parent dcba216 commit e40d12e
Show file tree
Hide file tree
Showing 5 changed files with 304 additions and 1 deletion.
162 changes: 162 additions & 0 deletions source/_posts/Backend/c++/gcc-makefile-c.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
---
title: 'gcc makefile c++'
date: 2022-12-06 14:04:44
categories:
- c++
tags:
- c++
---

## gcc

### 概念

`gcc``GNU Compiler Collection`(就是GNU编译器套件),也可以简单认为是编译器,它可以编译很**多种编程语言**(包括`C``C++``Objective-C``Fortran``Java`等等)。

### 特点

- 当你的程序只有**一个**源文件时,直接就可以用gcc命令编译它。

- 但是当你的程序包含很**多个**源文件时,用gcc命令逐个去编译时,你就很容易混乱而且工作量大

## make

所以出现了`make`工具,make工具可以看成是一个智能的批处理工具,它本身并没有编译和链接的功能,而是用类似于批处理的方式—通过调用`makefile`文件中用户指定的命令来进行编译和链接的。

### makefile

`makefile`简单的说就像一首歌的乐谱,make工具就像指挥家,指挥家根据乐谱指挥整个乐团怎么样演奏,make工具就根据makefile中的命令进行编译和链接的。

makefile命令中就包含了调用gcc(也可以是别的编译器)去编译某个源文件的命令。

makefile在一些简单的工程完全可以人工手下,但是当工程非常大的时候,手写makefile也是非常麻烦的,如果换了个平台makefile又要重新修改。

### cmake

这时候就出现了`cmake`这个工具,cmake就可以更加简单的生成makefile文件给上面那个make用。当然cmake还有其他功能,就是可以跨平台生成对应平台能用的makefile,你不用再自己去修改了。

可是cmake根据什么生成makefile呢?它又要根据一个叫CMakeLists.txt文件(学名:组态档)去生成makefile。

## 总结

CMake ———> makefile (包含调用gcc的命令)————> make工具 —————> 编译链接源文件

## 语法

```shell
gcc (选项) (参数)
```

### 选项

```shell
-o:指定生成的输出文件;
-E:仅执行编译预处理;
-S:将C代码转换为汇编代码;
-wall:显示警告信息;
-c:仅执行编译操作,不进行连接操作。
-l:用来指定程序要链接的库,-l参数紧接着就是库名
-I:寻找头文件的目录
```

### 参数

C源文件:指定C语言源代码文件。

### 实例

**常用编译命令选项**

假设源程序文件名为test.c

**无选项编译链接**

```shell
gcc test.c
```

`test.c` 预处理、汇编、编译并链接形成可执行文件。这里未指定输出文件,默认输出为 `a.out`

**选项 -o**

```shell
gcc test.c -o test
```

`test.c` 预处理、汇编、编译并链接形成可执行文件 `test``-o` 选项用来指定输出文件的文件名。

**选项 -E**

```shell
gcc -E test.c -o test.i
```

`test.c` 预处理输出 `test.i` 文件。

**选项 -S**

```shell
gcc -S test.i
```

将预处理输出文件 `test.i` 汇编成 `test.s` 文件。

**选项 -c**

```shell
gcc -c test.s
```

将汇编输出文件 `test.s` 编译输出 `test.o` 文件。

**无选项链接**

```shell
gcc test.o -o test
```

将编译输出文件 `test.o` 链接成最终可执行文件 `test`

**选项 -O**

```shell
gcc -O1 test.c -o test
```

使用编译优化级别1编译程序。级别为1~3,级别越大优化效果越好,但编译时间越长。

**多源文件的编译方法**

如果有多个源文件,基本上有两种编译方法:

假设有两个源文件为 `test.c``testfun.c`

**多个文件一起编译**

```shell
gcc testfun.c test.c -o test
```

`testfun.c``test.c` 分别编译后链接成 `test` 可执行文件。

**分别编译各个源文件,之后对编译后输出的目标文件链接。**

```shell
gcc -c testfun.c #将testfun.c编译成testfun.o
gcc -c test.c #将test.c编译成test.o
gcc testfun.o test.o -o test #将testfun.o和test.o链接成test
```

以上两种方法相比较,第一中方法编译时需要所有文件重新编译,而第二种方法可以只重新编译修改的文件,未修改的文件不用重新编译。

**加载动态链接库**

```shell
gcc hello.c -lpthread -o hello
```

**手动添加文件头路径**

```shell
gcc hello.c -lpthread -I /lib64/ -o hello
```
18 changes: 18 additions & 0 deletions source/_posts/Backend/c++/内存对齐.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
title: 内存对齐
date: 2022-11-20 13:16:35
categories:
- c++
tags:
- c++
---

## 内存对齐的目的

主要是由于 CPU 的访问内存的特性决定,CPU 访问内存时并不是以字节为单位来读取内存,而是**以机器字长为单位**,实际机器字长**由 CPU 数据总线宽度决定**的。实际 CPU 运行时,每一次控制内存读写信号发生时,CPU 可以从内存中读取数据总线宽度的数据,并将其写入到 CPU 的通用寄存器中。比如 32 位 CPU,机器字长为 **4 字节**,数据总线宽度为 32 位,如果该 CPU 的地址总线宽度也是为 3232 位,则其可以访问的地址空间为[0,0xffffffff]

内存对齐的主要目的是为了**减少 CPU 访问内存的次数****加大 CPU 访问内存的吞吐量**。假设读取 8 个字节的数据,按照每次读取 4 个字节的速度,则 8 个字节需要 CPU 耗费 2 次读取操作。CPU 始终以字长访问内存,如果不进行内存对齐,很可能增加 CPU 访问内存的次数。

![](https://pic.leetcode-cn.com/1661173255-dXASLi-1_5_1.png)

比如以上在读取变量 `b` 时,如果不进行内存对齐的话,会导致 `CPU` 读取次数为 `2`,在内存对齐的情况下,只需读取一次即可。
51 changes: 51 additions & 0 deletions source/_posts/Backend/c++/变量定义.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
title: 变量定义
date: 2022-11-20 13:03:16
categories:
- c++
tags:
- c++
---

## 四种变量

- **全局变量**:具有全局作用域。全局变量只需在一个源文件中定义,就可以作用于所有的源文件。其他不包含全局变量定义的源文件需要用 extern 关键字再次声明这个全局变量。
- **静态全局变量**:具有文件作用域。它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被 static 关键字修饰过的变量具有文件作用域。这样即使两个不同的源文件都定义了相同名字的静态全局变量,它们也是不同的变量。
- **局部变量**:具有局部作用域。它是自动对象(auto),在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回,局部变量对于函数外部的程序来说是不可见的。当然内部实际更复杂,实际是以 {} 为作用域的。
- **静态局部变量**:具有局部作用域。它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在,它和全局变量的区别在于全局变量对所有的函数都是可见的,而静态局部变量只对定义自己的函数体始终可见, 只有定义**该变量的函数内部**可以使用访问和修改该变量。

```c++
#include <iostream>
using namespace std;
int g_var = 0; // 全局变量
static char *gs_var; // 静态全局变量

int main() {
int var; // 局部变量
static int s_var = 0; // 静态局部变量
return 0;
}
```

```c++
#include <iostream>
using namespace std;
extern int g_var = 0; // 访问全局变量
// extern static char *gs_var; 无法访问静态全局变量

int test() {
g_var = 1;
}
```

## 生命周期

- **全局变量**: 全局变量在整个程序运行期间都会一直存在,都可以随时访问,当程序结束时,对应的变量则会自动销毁,内存会被系统回收。
- **局部变量**: 局部变量的生命周期仅限于函数被调用期间,当函数调用结束时,该变量会自动销毁。
- **静态局部变量**:实际上静态局部变量的作用域仅限于函数内部,它的作用域与局部变量相同,但实际上该变量在程序运行期间是一直存在的,生命周期贯穿于整个程序运行期间。局部静态变量只能被初始化一次。

## 注意

- 静态变量和栈变量(存储在栈中的变量)、堆变量(存储在堆中的变量)的区别:静态变量会被放在程序的静态数据存储区(.data 段,bss 段,rodata 段)中(静态变量会自动初始化),这样可以在下一次调用的时候还可以保持原来的赋值。而栈变量或堆变量不能保证在下一次调用的时候依然保持原来的值。
- 静态变量和全局变量的区别:静态变量仅在变量的作用范围内可见,实际是依靠编译器来控制作用域。全局变量在整个程序范围内都可可见,只需声明该全局变量,即可使用。
- 全局变量定义在不要在头文件中定义:如果在头文件中定义全局变量,当该头文件被多个文件 include 时,该头文件中的全局变量就会被定义多次,编译时会因为重复定义而报错,因此不能再头文件中定义全局变量。一般情况下我们将变量的定义放在 .cpp 文件中,一般在 .h 文件使用extern 对变量进行声明。
39 changes: 39 additions & 0 deletions source/_posts/Backend/c++/编译与链接.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: 编译与链接
date: 2022-11-20 11:23:15
categories:
- c++
tags:
- c++
---

## 编译的目的

我们常见的C/C++ 语言,CPU 是无法理解的,这就需要将我们编写好的代码最终翻译为机器可执行的**二进制指令**,编译的过程本质上也即是翻译的过程,当然中间涉及的细节非常复杂。

## 编译过程

编译器读取源文件 cpp,并将其翻译为可执行文件「ELF」,ELF 文件可以经过操作系统进行加载执行。常见的编译过程分为四个过程:**编译预处理、编译、汇编、链接**

![](https://pic.leetcode-cn.com/1661172766-jkudLd-1_1_1.png)

- 编译预处理:在预编译过程中主要处理源代码中的预处理指令,比如引入头文件(#include),去除注释,处理所有的条件编译指令(#ifdef, #ifndef, #else, #elif, #endif),宏的替换(#define),添加行号,保留所有的编译器指令;
- 编译:针对预处理后的文件进行**词法分析、语法分析、语义分析、符号汇总、汇编代码**生成,并针对程序的结构或者特定的 CPU 平台进行**优化**,其中涉及的过程较为复杂。简单来说编译的过程即为将 .cpp 源文件翻译成 **.s** 的汇编代码;
- 汇编:将汇编代码 .s 翻译成机器指令 **.o** 文件,一个 .cpp 文件只会生成一个 .o 文件;
- 链接:汇编程序生成的目标文件即为 .o 文件,单独的 .o 文件可能无法执行。因为一个程序可能由多个源文件组成,此时就存在多个 .o 文件。文件 A 中的函数引用了另一个文件 B 中定义的符号或者调用了某个库文件中的函数,这就需要链接处理。那链接的目的就是**将这些文件对应的目标文件连接成一个整体**,从而生成一个可被操作系统加载执行的ELF 程序文件。

## 静态链接与动态链接

- **静态链接**:代码在生成可执行文件时,将该程序所需要的**全部**外部调用函数全部**拷贝**到最终的**可执行程序文件中**,在该程序被执行时,该程序运行时所需要的全部代码都会被装入到该进程的虚拟地址空间中。在 Linux 系统下,静态链接库一般以 **.a** 文件,我们可以将多个 .o 文件链接成一个静态链接库。
- **动态链接**:代码在生成可执行文件时,该程序所调用的部分程序被放到动态链接库或共享对象的某个目标文件中,链接程序只是在最终的可执行程序中**记录了共享对象的名字等一些信息**,最终生成的 ELF 文件中并**不包含**这些调用程序二进制指令。在程序执行时,当需要调用这部分程序时,操作系统会从将这些动态链或者共享对象进行加载,并将全部内容会被**映射**到该进行运行的虚拟地址的空间。在 Linux 系统下,动态链接库一般以 **.so** 文件,我们可以将多个 .o 文件链接成一个动态链接库。

### 优缺点

| | 静态链接 | 动态链接 |
| -------- | ------------------------------------------------------------ | ---------------------------------------- |
| 空间 | 浪费空间,每个可执行程序都会有目标文件的一个副本 | 动态链接节省内存 |
| 更新 | 如果目标文件进行了更新操作,就需要重新进行编译链接生成可执行程序(更新困难) | 更新方便 |
| 效率 | 执行的时候运行速度快,因为可执行程序具备了程序运行的所有内容 | 相比静态链接会有一定的性能损失 |
| 连接方式 | 静态链接是由连接器完成的 | 动态链接最终是由操作系统来完成链接的功能 |

> 动态链接在不同的操作系统下可能由不同的实现原理,比如在 Linux 系统下,动态链接库通常以 **.so** 文件存在,在 windows 下同下,动态链接库一般以 **.dll** 文件存在。
35 changes: 34 additions & 1 deletion source/_posts/Backend/operating-system/内存管理.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ tags:

![](https://img-blog.csdnimg.cn/8904fb89ae0c49c4b0f2f7b5a0a7b099.png)

### Linux的虚拟地址空间分布
## Linux的虚拟地址空间分布

在 Linux 操作系统中,虚拟地址空间的内部又被分为**内核空间和用户空间**两部分,不同位数的系统,地址空间的范围也不同。比如最常见的 32 位和 64 位系统,如下所示:

Expand Down Expand Up @@ -135,3 +135,36 @@ tags:
- 堆段,包括动态分配的内存,从低地址开始向上增长;
- 文件映射段,包括动态库、共享内存等,从低地址开始向上增长;
- 栈段,包括局部变量和函数调用的上下文等。栈的大小是固定的,一般是 `8 MB`。当然系统也提供了参数,以便我们自定义大小;

```cpp
#include <iostream>
using namespace std;
/*
说明:C++ 中不再区分初始化和未初始化的全局变量、静态变量的存储区,如果非要区分下述程序标注在了括号中
*/
int g_var = 0; // g_var 在全局区(.data 段)
char *gp_var; // gp_var 在全局区(.bss 段)

int main()
{
int var; // var 在栈区
char *p_var; // p_var 在栈区
char arr[] = "abc"; // arr 为数组变量,存储在栈区;"abc"为字符串常量,存储在常量区
char *p_var1 = "123456"; // p_var1 在栈区;"123456"为字符串常量,存储在常量区
static int s_var = 0; // s_var 为静态变量,存在静态存储区(.data 段)
p_var = (char *)malloc(10); // 分配得来的 10 个字节的区域在堆区
free(p_var);
return 0;
}
```

### 堆和栈的区别

| |||
| -------- | -------------- | -------------------------------- |
| 管理方式 | 编译器自动管理 | 程序员管理 |
| 空间大小 | 较小 | 较大 |
| 碎片问题 | 连续,无碎片 | 不连续,有碎片 |
| 分配效率 || 低(内存碎片,内核态用户态切换) |
| 生长方向 | 想下 | 向上 |

0 comments on commit e40d12e

Please sign in to comment.