November 04, 2016

C语言中的堆与栈

引子

C 比较难理解指针的概念,经常与数组放在一起来讲,比如

char msg1[] = "hello world";
char *msg2  = &msg1[0];

此时, msg1msg2 指向同一个内存位置,但由于类型的不同,sizeof 结果也不同 前者为 12,后者为 8(64位机器)

因此我们得出结论,数组可以转换为指针,通过对数组元素引用(取址)生成指针,但对于下面的代码

char* foo()
{
	char msg[] = "hello world";
	return msg;
}

char* bar()
{
	char* msg = "hello world";
	return msg;
}

int main()
{
	printf( "%s\n", foo() );
	printf( "%s\n", bar() );
	return 0;
}

调用 foo 时,会报段错误;而 bar 却能够正常获取结果。

造成区别的原因在于:

  1. 数组空间申请于栈(Stack),而栈的内容在函数返回被弹空
  2. 指针初始化指向字符串位于堆(Heap),而堆的内容是全局的

栈与堆的对比

栈的特点

  • 栈内的变量是本地的、临时的,会在调用时从栈里被分配,无需手动管理
  • 栈的大小是有限的,过大的变量无法在栈里使用
  • 可以用 alloca 从栈里申请空间,但大小受限,且不可调整

堆的特点

  • 堆是进程全局的,一般通过 malloc()free() 主动申请和释放,因为忘记释放会造成内存泄漏
  • 不限制容量(受物理内存限制),可以用 realloc 调整大小
  • 全局的

关于堆与栈的用法思考

  • 变量大小需要变化,只能用堆
  • 如果变量占用空间过大,只能用堆,但在可能时候加 const 保护(放在 .rodata 中)
  • 栈在函数参数和返回值传递中有很重要作用
  • 在函数式思维中,堆是造成系统状态复杂的主要原因
  • 函数式中,将变量的状态维护在栈上,而非堆上,减少对外界全局因素的依赖
  • 如果想在一个函数中,返回一个数组的内容,可以将之声明为 static,但对于并行造成困难
  • 堆的操作在函数式中也是不可避免的,例如 list 列表中添加新的元素需要从堆中申请,但由于有 gc 和刻意避免用户操作内容所以没有明显的问题

参考资料