堆、栈与指针
前言
堆、栈在计算机领域是亘古不变的热门话题,归根结底它们和编程语言无关,都是操作系统层面的内存划分,后面尝试简单地拆开这几个概念,谈谈我对它们的理解。
栈
每个函数中每个值在栈中都是独占的,不能在其他栈中被访问。每个方法片(function frame)都有一个自己的独享栈,这个栈的生命周期随着方法开始结束诞生与消逝,在方法结束时候会被释放掉,较之于堆,栈的优势是比较轻量级,随用随弃,存活期跟随着函数。
堆
通俗的讲,假如说栈是各个函数的一栋私人住宅,堆就是一个大型的人民广场,它可以被共享。堆作为一个全局访问块,它的空间由GC(拆迁大队)管理。
The heap is not self cleaning like stacks, so there is a bigger cost to using this memory. Primarily, the costs are associated with the garbage collector (GC), which must get involved to keep this area clean.
翻译过来,区别于栈在函数调用结束时候就释放掉,堆不会自动释放,堆空间的释放主要来自于垃圾回收操作。
GC(Garbage collection)
垃圾回收, 垃圾回收具有多种策略,一般来说,每一个存在于堆中,但不再被指针所引用的变量,都会被回收掉。“These allocations put pressure on the GC because every value on the heap that is no longer referenced by a pointer, needs to be removed.”由于垃圾回收涉及内存操作,往往需要考虑许多因素,所幸经过漫长演变,前人种树,有些编程语言垃圾回收策略已经足够强大(Java,Go),我们大部分时候不需要去干涉内存清理,把这部分工作交给底层调度器。
分配到堆内存有个弊端是它会为下一次GC增加压力,好处是可以被其他栈所共享,下列情景编译器会倾向于将它放在堆中存储:
- 尝试申请一个较大的结构体/数组
- 变量在一定的时间内还会被使用
- 编译期间不能确认大小的变量申请
指针
指针,本质上和其他类型一样,只不过它的值是内存地址(引用),我的理解是内存块的门牌号。有句经常被提及的话:
什么时候该使用指针取决于什么时候要分享它。
Pointers serve one purpose, to share a value with a function so the function can read and write to that value even though the value does not exist directly inside its own frame.
指针是为了让变量在不同函数方法块(栈区间)之间分享,并且提供变量读写操作。
结合上述的理解,指针指的是内存地址,堆是共享模块,指针是为了共享同一块内存片。在Go语言中,所有传参都是值传递,指针也是通过传递指针的值。
程序实例 指针共享
主函数
func main() {
var v int = 1
fmt.Printf("# Main frame: Value of v:\t\t %v, address: %p\n", v, &v)
PassValue(v, &v)
fmt.Printf("# Main frame: Value of v:\t\t %v, address: %p\n", v, &v)
}
子函数