C++学习笔记 22 堆栈的区别

一、栈

  1. 栈通常是一个预定义大小的内存区域,通常约为2M字节左右。

  2. 堆也是一个预定义了默认值的区域。但是它的大小可以随着程序的执行而增长、改变。

  3. 重要的是,这2个内存区域的实际位置(物理位置)在我们的RAM中完全一样。

  4. arr 和 a变量的内存数据挨着,两者之间有一些字节,这只是因为在调试模式下运行,实际上只是添加了些安全守卫(safety guards, 在所有的变量周围),以确保我们不会溢出所有的变量,在错误的内存中访问它们。

  5. 所以,在内存中,栈变量都挨得很近,因为实际发生的是,当我们在栈中分配变量时,发生的是:栈指针也就是栈顶部的指针,基本上就是移动这么多字节。

  6. 如果我想分配一个4个字节的整数,我们把指针移动4个字节;如果我想分配一个数组,就像这里有5个整数,就是4 x 5 = 20字节,最后是Vector3, 有3个int, 3 x 4 = 12 字节,我们只是移动栈指针。

  7. 内存实际上是互相叠加存储的,就像一个栈。现在大多数栈的实现中,栈是倒着来的,更高的内存地址,存储第一个变量。

  8. 栈的思路是:把东西堆到一起,这就是为什么栈分配非常快,它就像一条CPU指令,我们所做的就是移动栈指针,然后我们返回栈指针的地址。

  9. 在栈中,一旦你在栈中分配内存的作用域结束,你在栈中分配的所有内存都会被弹出,会被释放。

  10. 在栈上分配内存、存储变量的额外好处:它们在内存中挨得很近,因此,它们可以被放到CPU缓存线(CPU Cache Line:可以理解为CPU Cache中的最小缓存单位)

  11. 如果我们是在堆中创建,可能会产生一些cache miss,(CPU要访问的数据在缓存中有,称谓 Cache hit,反之,称为Cache miss)。相比之下,在栈中分配,可能不会得到cache miss。在我们请求第一个栈上变量之后。

  12. 有一些cache miss 对比 没有cache miss,不是什么大问题,你可能完全不会注意到区别,但是如果我们如果要处理上百万的cache misses, 那就是大问题了。但如果就是少量的cache misses, 区别可以忽略不计。

二、 堆

  1. new 实际上是调用了malloc函数, memory allocate的缩写,会调用底层操作系统或平台的特定函数,这将在堆上为你分配内存。

  2. 当你启动你的应用时,你会得到一定数量的物理RAM分配给你。你的程序会维护一个叫做空闲列表(free list)的东西,它是跟踪哪些内存块是空闲的,它们在哪儿。

  3. 所以当你需要动态内存的时候,使用动态堆内存,当你使用malloc请求堆内存的时候,它可以浏览空闲列表,然后找到一块空闲内存,至少和你需要的一样大,然后给你一个它的指针,然后还要记录一些东西,例如分配的大小,和它现在被分配的情况,你不能再使用那块内存了(有一堆记录要做)。

  4. malloc实际实现取决于实现方法,它是一个很大很重的function,需要做很多记录,不仅仅是得到内存那么简单。

  5. 更糟糕的情况是:如果你想要非常多的内存,超过了空闲列表,超过了操作系统给你的初始分配,这个时候,你的程序,你的应用需要询问你的操作系统,我需要更多的内存,这是非常麻烦的,潜在的成本是巨大的。

  6. 总的来说,在堆上分配内存,有一大堆的事情,而在栈上分配内存,就像一条CPU指令,非常快速。

  7. 我希望大家都明白,事实上,你应该尽量在栈上分配,如果可能的话。

  8. 在堆上分配的唯一原因:是你不能够在栈上分配。比如需要这个生命周期比函数、或你处理的作用域更长;或者你需要个大数据,比如50M,这就不适合在栈上分配,你不得不在堆上分配。

  9. 只要可以,你就应该在栈上分配内存,因为它就像一条CPU指令一样,非常快。这是非常非常真实的性能差异。

  10. 性能的不同是因为分配的不同,所以理论上讲,如果你预先分配,比如4G内存块,在你的程序运行之前,在堆上。然后你要从预先分配的4G内存块中再进行堆数据分配,那么这就和栈分配基本上一样了,唯一需要处理的是cpu cache miss (缓存不命中)的问题,但他们的数量可能不够造成麻烦。

  11. 所以当你new对象时,你需要检查空闲列表,请求内存,然后记录这些,这些就是堆相比于栈慢的地方;而实际的访问(指CPU、缓存)通常可以忽略不计,是通常,但不总是。

  12. 我们可能会讨论更多CPU缓存优化的内容,如果你正在编写一个100万个元素的集合,然后每个元素都会cache miss,你会看到一个非常真实的性能差异。如果你所有的东西都是连续的或者碎片的。

三、游戏案例讨论

当我们真正开始游戏引擎系列时,我们讲不得不花大量时间讨论分配问题,因为这在现实应用中很重要,而现实世界的应用程序是实时的应用程序,所以这对游戏来说非常重要,不能连续地一帧一帧地分配,因为这会很慢。所以我们必须想出一些聪明的内存管理技术,如果我们想要我们的游戏更有效率的话。所以我们一定要在游戏引擎系列讨论这个问题。


学习代码:

#include<iostream>

struct Vector3 {
	int x, y, z;
	Vector3(): x(10), y(20), z(30) {}
};

void testStackHeap() {
	//栈
	int a = 10;
	int arr[5];
	Vector3 v1;

	//堆
	int* heapA = new int(10);
	int* heapArr = new int[5];
	Vector3* heapV = new Vector3();
	
	for (int i = 0; i < 5; i++) {
		arr[i] = i + 1;
	}
	for (int i = 0; i < 5; i++) {
		heapArr[i] = i + 1;
	}
}

int main() {
	testStackHeap();
	std::cin.get();
}
posted @ 2025-12-17 14:13  超轶绝尘  阅读(2)  评论(0)    收藏  举报