🌱C++数组大小为何必须是编译时常量
C++规定:数组大小必须是编译时常量,可是这是为什么呢?性能因素似乎是被首要考虑的。
在C++中,如果我们声明一个常规数组:
int myArray[10];
这个数组是分配在栈上的。
栈内存的分配和释放速度极快,因为他只需要移动一下栈顶指针。 为了实现这种高效,编译器必须在编译代码的时候就知道需要为这个数组预留多少空间。 这里的 10 就是一个编译时常量。
如果我们使用变量作为数组长度,那就没有办法实现 “在编译时就知道数组长度”。
栈的特点是“后进先出”,新数据永远放在顶部,需要的数据也是从顶部拿走。 在CPU中,有一个特殊的寄存器专门用来管理栈,叫做 栈指针(Stack Pointer, SP)。 当一个函数被调用时,它会在栈上获得一块属于自己的专属内存,这块区域被称为栈帧(Stack Frame)。 一个函数的栈帧里包含了:它接收到的参数、它所有的局部变量、一些管理信息。
栈的高效体现在栈帧的创建和销毁过程上,这个过程不是通过复杂的操作系统函数调用完成的,而是通过几条极其简单的CPU汇编指令。 也就是说,栈的内存分配和释放,不需要像堆内存那样使用复杂的算法,仅仅需要对SP寄存器进行一次加法或减法运算。 对CPU来说,执行一条整数加减法指令通常只需要一个时钟周期,这已经是计算机能做的最快的操作之一了。
高效也体现在内存碎片上,因为严格遵守后进先出原则,内存块总是从一段被连续地分配和释放,这就导致了栈永远不会出现内存碎片。
还有一个非常关键的性能优势是,栈拥有极佳的缓存局部性。 一个函数的所有局部变量都被分配在它的栈帧里面,这意味着在物理上它们是紧密相邻、连续存放的。 这在现在CPU的多级缓存设计上将极具优势,因为可以把一个栈帧一次性加载到速度最快的L1缓存中,从而拥有极高的缓存命中率。这个特性是堆无法实现的,因为其在物理上就无法保证是连续的内存。