值放在栈上还是堆上
值放在栈上还是堆上?
很多初学者经常在学编程语言中,总是会有这样的误解, 认为值类型放在栈上, 引用类型放在堆上面,这种方式就相等于考试背诵答案
下面的代码以rust为例子, 但是不会涉及到太多
1 | let str = "hello, world".to_string(); |
首先”hello, world”作为一个常量, 在编译的时, 会放入.rodata
段中, 当执行to_string() 的时候会在堆上分配一段空间, 将这个字符串拷贝进去, 之后我们使用赋值操作, s是栈上的一个变量, 这个变量存储这该字符串在堆上的地址, 字符串的长度, 字符串的容量。
上面我提到变量str放在栈上, 而字符串放在堆上, 那么究竟什么样子的数据类型会放在堆上呢,你学校的老师会告诉那个经典的答案,基本类型(primitive type)存储在栈上,对象存储在堆上。
栈
栈是程序运行的基础, 当一个变量或者被创建的时候, 在栈上就会分配一段空间, 当函数退出的时候, 该块空间就会被弹出去, 比如在C语言中, 随着main函数的层层调用, 栈也会一层层的进行扩展, 当函数调用结束, 栈也就会一层层退出, 将内存释放。
那么我们如何确定每一个栈帧的大小呢? 这个项工作主要归功于编译器, 编译器会在编译的时候确定栈上面放哪些局部变量, 确定每一个局部变量对应的大小。
所以在栈上存放的数据是需要明确大小的, 因为字符串我们无法在编译时确定大小的, 所以我们不可以将其放在栈上面。
同时放在栈上面还有一个问题, 那就是我们操作系统给我们分配的最大栈空间是有限制的, 如果超过这个最大限制, 会造成栈溢出。
堆
当我需要动态大小的内存的时候, 只能使用堆, 比如可变长数组, 列表, 哈希值。
1 | var a = new Person(); |
同时堆上分配内存的时候, 往往会预留一部分内存, 以备用, 因为在堆上申请内存会频繁的请求操作系统的, 是非常耗时的。
除了编译时无法确定大小之外, 动态声明周期的内存也需要分配在堆上, 栈上面的内存会在结束后自动释放, 因为确定大小,编译器就自动的可以做这些事情,而堆上的内存就需要手动的释放, 这就使得堆上的内存有灵活的生命周期, 可以在不同的调用栈之间进行共享。
但是放在堆上的内存如果没有释放, 就会造成内存泄漏, 程序运行的越久, 就会越来越吃内存, 最终会因为占满内存而被操作系统终止