内存分区模型

C++程序在执行时,将内存大方向划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
区域 存储内容 生命周期 访问速度
栈区 局部变量、函数参数、返回地址 函数结束时自动释放 最快
堆区 new/malloc动态分配的对象 手动管理或程序结束回收 较慢
全局区 全局/静态变量、常量、虚函数表 程序启动到结束
代码区 机器指令、只读常量(字符串字面量) 程序运行期间 只读
代码区(Text Segment)
    用途:存储编译后的二进制机器指令(函数体代码)
    特性:
      只读属性(防止程序意外修改指令)
      在程序运行前完成分配
      共享内存机制(多个实例共享同一份代码)
全局区(Global/Static Segment)
    用途:存放全局变量、静态变量(static)、常量
    细分区域:
      .data 段:已初始化的全局/静态变量
      .bss 段:未初始化的全局/静态变量(默认置零)
      常量区:字符串常量(如 "Hello")、const 修饰的全局常量
    生命周期:程序启动时分配,结束时释放
栈区(Stack)
    用途:存储函数参数、局部变量、函数返回地址
    特性:
      自动管理(编译器分配/回收)
      大小固定(Windows默认约1MB,Linux默认约8MB)
      后进先出(LIFO)结构
      快速访问(通过寄存器直接操作)
堆区(Heap)
    用途:存储动态分配内存(通过 new/malloc 申请)
    特性:
      手动管理(需显式使用 delete/free 释放)
      空间远大于栈区(受系统可用内存限制)
      访问速度慢于栈区(通过指针间接访问)
      可能产生内存碎片

内存四区意义:不同区域存放的数据,赋予不同的生命周期,提供更大的灵活编程

典型对比场景

1
2
3
4
5
6
7
int global_var; // 全局区
void func() {
static int static_var; // 全局区
int local_var; // 栈区
int* heap_var = new int(10); // 堆区
const char* str = "Literal"; // "Literal"在全局区,指针在栈区
}

⚠️ 注意事项

  • 栈溢出:递归深度过大时可能触发 Stack Overflow
  • 内存泄漏:堆区未正确释放内存会导致资源耗尽
  • C++11后推荐使用智能指针(如 unique_ptr)管理堆内存

地址分配展示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using namespace std;

// 全局变量
int g_a = 10;
int g_b = 20;

// 全局常量
const int g_c_a = 10;
const int g_c_b = 20;

int main()
{
// 局部变量地址
int a = 10;
int b = 20;

// 常量
const int c_a = 10;
const int c_b = 20;
string str = "hello world";

// 静态变量
static int s_a = 10;
static int s_b = 20;

cout << "局部变量a的地址为:" << "\t" << (long long)&a << endl; // 98882747140
cout << "局部变量b的地址为:" << "\t" << (long long) &b << endl; // 98882747172

cout << "局部常量c_a的地址为:" << "\t" << (long long)&c_a << endl; // 98882747204
cout << "局部常量c_b的地址为:" << "\t" << (long long)&c_b << endl; // 98882747236
cout << "字符串常量str的地址为:" << "\t" << (long long)&str << endl; // 98882747272

cout << "全局常量g_c_a的地址为:" << "\t" << (long long)&g_c_a << endl; // 140699421043728
cout << "全局常量g_c_b的地址为:" << "\t" << (long long)&g_c_b << endl; // 140699421043732
cout << "全局变量g_a的地址为:" << "\t" << (long long) &g_a << endl; // 140699421061120
cout << "全局变量g_b的地址为:" << "\t" << (long long) &g_b << endl; // 140699421061124

cout << "静态变量d_a的地址为:" << "\t" << (long long) &s_a << endl; // 140699421061128
cout << "静态变量d_b的地址为:" << "\t" << (long long) &s_b << endl; // 140699421061132

// 以上输出都是举例,实际最终运行结果不同
// 不过可以得出结论:
// 局部变量、局部常量、字符串常量的地址都是相近的
// 全局变量、全局常量、静态变量地址都是相近的


return 0;
}

记忆技巧:

  • 局部地址相近
  • 全局地址相近

程序运行前

在程序编译后,生成了.exe可执行程序,未执行该程序前分为两个区域:

代码区:

​ 存放CPU执行的机器指令

​ 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可

​ 代码是只读,使其只读的原因是防止程序意外地修改了它的指令

全局区:

​ 全局变量和静态变量存放在此

​ 全局区还包含了常量区,字符串常量和其他常量也存放在此

该区域的数据在程序结束后由操作系统释放

程序运行后

栈区

栈区:

  • 由编译器自动分配释放,存放函数的参数值,局部变量等
  • 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int* func()
{
int a = 10;
return &a;
}

int* another_function() {
int a = 10;
return &a;
};

int main() {
int* p = func();

cout << *p << endl; // 10
std::vector<int> v(1000); // 故意占用栈空间
cout << *p << endl; // 32759

return 0;
}

堆区

堆区:

  • 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
  • 在C++中主要利用new在堆区开辟内存

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int* func() {
int* a = new int(10);
return a;
}

int main() {
int* p = func();

cout << *p << endl; // 10
std::vector<int> v(1000); // 故意占用栈空间
cout << *p << endl; // 10 不受影响

return 0;
}

new操作符

C++中利用new操作符在堆区开辟数据

堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete

语法:new 数据类型

示例:

基础用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <string>

using namespace std;

int main() {

// 在堆区创建整型数据
// new返回是该数据类型的指针
int* p1 = new int(10);
cout << *p1 << endl;
delete p1;
// cout << *p1 << endl; // 野指针访问

// new数组
int* p2 = new int[10]; // [10] 表示数组长度
for (int i = 0; i < 10; i++)
{
p2[i] = i;
cout << p2[i] << " "; // 0 1 2 3 4 5 6 7 8 9
}
// 释放堆区数组
delete[] p2;

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <string>
#include <vector>

using namespace std;

int* func_int() {
int* a = new int(10); // new返回的是该数据类型的指针
return a;
}

bool* func_bool() {
bool* a = new bool(true); // new返回的是该数据类型的指针
return a;
}

string* func_string() {
string* a = new string("hello"); // new返回的是该数据类型的指针
return a;
}

float* func_float() {
float* a = new float(3.14); // new返回的是该数据类型的指针
return a;
}


int main() {
int* int_p = func_int();
bool* bool_p = func_bool();
string* string_p = func_string();
float* float_p = func_float();

std::vector<int> v(1000); // 故意占用栈空间
cout << *int_p << endl; // 10 不受影响
cout << *bool_p << endl;
cout << *string_p << endl;
cout << *float_p << endl;



return 0;
}

引用

作用:给变量起别名(实际上就是让多个变量指向同一个内存地址)

语法:数据类型& 别名 = 原名

区别:取址符为数据类型 变量名 = &变量名

示例:

1
2
3
4
5
6
7
int main() {
int a = 10;
int &b = a; // 编译器生成:int* const b = &a;

cout << "a:" << a <<endl; // 10
cout << "b:" << b <<endl; // 10 同一内存地址的数据
}

引用变量b在底层是通过常量指针int* const)实现的,存储的是变量a的地址

任何对b的操作(如b = 20)会被编译为*b = 20(自动完成指针解引用)

引用注意事项

  • 引用必须初始化
  • 引用在初始化后,不可以改变

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <string>


using namespace std;

int main() {

int a = 10;
int b = 20;
// int &c; // 报错,引用变量必须初始化
int &c = a;
c = b; // 这是赋值操作,不是更改引用

// &c = b; // 报错,不可以改变引用变量的引用对象

cout << "a: " << a << endl; // 20
cout << "b: " << b << endl; // 20
cout << "c: " << c << endl; // 20

return 0;
}

引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <string>


using namespace std;

// 交换函数
// 值传递方式
void swap(int a,int b) {
int tmp = a;
a = b;
b = tmp;
};

// 地址传递方式
void swap_byPointer(int* a, int* b) {
int tmp = *a;
*a = *b;
*b = tmp;
}

// 引用传递方式
void swap_byRef(int& a, int& b) { // 相当于给实参起了个别名叫a
int tmp = a;
a = b;
b = tmp;
}

int main() {

int a = 10;
int b = 20;
swap(a,b);
cout << "a = " << a << endl; // 10
cout << "b = " << b << endl; // 20

int c = 10;
int d = 20;
swap_byPointer(&c,&d);
cout << "c = " << c << endl; // 20
cout << "d = " << d << endl; // 10

int e = 10;
int f = 20;
swap_byRef(e,f);
cout << "e = " << e << endl; // 20
cout << "f = " << f << endl; // 10

return 0;
}

通过引用参数产生的效果同按地址传递是一样的

引用做函数返回值

作用:引用是可以作为函数的返回值存在的

注意:不要返回局部变量引用

用法:函数调用作为左值

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <string>

using namespace std;

// test01和test02没有区别
int& test01() {
int a = 10;
int &b = a;
return b;
}

int& test02() {
int a = 10;
return a;
}

// test03,将局部变量变为静态变量
int& test03() {
static int a = 10;
return a;
}

int main() {

int &a_01 = test01(); // 相当于给函数内的变量a起了个别名
int &a_02 = test02();
int &a_03 = test03();
vector<int> v(1000); // 故意占用栈空间
cout << a_01 << endl; // 悬垂引用
cout << a_02 << endl; // 悬垂引用
cout << a_03 << endl; // 10

// 引用函数的返回值可以作为左值
test03() = 10000;
cout << a_03 << endl;

return 0;
}

当函数声明为返回int&时,任何返回的int变量都会隐式转换为引用(即使未显式使用&)。其他类型同理

当函数的返回值为引用类型时,是可以作为变量的左值的(也就是对函数内被引用的值重新赋值)

引用的本质

之前已经提到过引用本质,这一章会详细说明

本质:引用的本质在C++内部实现是一个指针常量

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

using namespace std;

void func(int& ref) {
ref = 100; // ref是引用,转换为*ref = 100
}

int main() {

int a = 100;

// 自动转换为int* const ref = &a; 指针常量是指针指向不可改,也说明了为什么引用不可更改
int& ref = a;
ref = 20; // 内部发现ref是引用,自动帮我们转换为:*ref = 20;

cout << "a:" << a << endl; // 20
cout << "ref:" << ref << endl; // 20

func(ref);

return 0;
}

C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但所有的指针操作编译器都会帮助程序员完成

常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加const修饰形参,防止形参改变实参

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <string>

using namespace std;


void func(const int& ref) {
//ref += 100; // 不可修改
cout << ref << endl;
}

int main() {

//int& ref = 10; // 无法将临时值(字面量 10)直接绑定到非常量左值引用 int&
const int& ref = 10; // 常量引用可绑定到右值,非常量引用不能

cout << ref << endl; // 10

func(100);

return 0;
}

需要注意,常量引用是可以直接赋值(字面量 100、表达式结果、临时对象)的,非常量引用则会报错

函数进阶

函数默认参数

在C++中,函数的形参列表中的形参是可以有默认值的

语法:返回值类型 函数名 (参数 = 默认值) {}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
#include <string>

using namespace std;

// 1. 如果某个位置参数有默认值,那从这个位置开始,后面的参数都必须有默认值
// 2. 如果函数声明有默认值,函数实现的时候就不能有默认参数
int func_01(int a, int b = 10, int c = 20) {
return a + b + c;
}

int func_02(int a = 10, int b = 10);
int func_02(int a, int b) {
return a + b;
}

int main() {
int getFunc01 = func_01(1);
cout << getFunc01 << endl; // 31
getFunc01 = func_01(1, 2); // 原默认值b = 10,现在b = 2
cout << getFunc01 << endl; // 23

int getFunc02 = func_02();
cout << getFunc02 << endl; // 20


return 0;
}

函数占位参数

C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置

语法:返回值类型 函数名 (数据类型) {}

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <string>

using namespace std;

// 占位参数
void func(int a, int) {
cout << "Hello,Im Func;" << endl;
}



int main() {
// func(10); // 报错
// func(10, "Hello"); // 错误
func(10, 10);

return 0;
}

函数重载

在基础篇中已经介绍了基本的函数重载使用,这一章将详细说明

作用:函数名可以相同,提高复用性

函数重载满足条件:

  • 同一个作用域下
  • 函数名称相同
  • 函数参数类型不同,或者个数不同或者顺序不同

注意:函数的返回值不可以作为函数重载的条件

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <iostream>
#include <string>

using namespace std;

// 同一个作用域
void func(int a, int b) {
cout << a << " ";
cout << b <<endl;
}

void func(int a, int b, int c) {
cout << a << " ";
cout << b << " ";
cout << c << endl;
}

void func(int a, string b) {
cout << a << " ";
cout << b << endl;
}

void func(string a, int b) {
cout << a << " ";
cout << b << endl;
}

//int func(int a, int b) {} // 错误



int main() {
func(1, 2); // 第一个函数
func(1, 2, 3); // 第二个函数
func(1, "hello"); // 第三个函数
func("hello", 1); // 第四个函数


return 0;
}

函数重载注意事项

  • 引用作为重载条件
  • 函数重载碰到函数默认参数

示例1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <string>

using namespace std;

void func(int& a) {
cout << "func(int& a)函数调用了" << endl;
}

void func(const int& a) {
cout << "func(const int& a)函数调用了" << endl;
}

int main() {
int a = 10;
func(a); // func(int& a)函数调用了
func(10); // func(const int& a)函数调用了

const int b = 10;
func(b); // func(const int& a)函数调用了

return 0;
}

示例2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <string>

using namespace std;

// 函数重载避免有默认值
void func(int a) {
cout << "func(int a)函数调用了" << endl;
}

void func(int a, int b = 10) {
cout << "func(int a, int b = 10)函数调oked" << endl;
}



int main() {
func(10); // 报错

return 0;
}