预计阅读本页时间:-
手动内存分配
大多数现代编程语言都允许程序员在程序执行的时候从堆(与栈相对)中分配动态内存。看看例3-1中的C程序:
例3-1:分配内存的样例程序
![阅读 ‧ 电子书库](https://book.vik.im/contents/771570957898/images/00048.jpeg)
当程序执行的时候,函数的局部变量(如main的argc和argv)都是在执行栈中,执行栈存储的是程序执行的过程。动态分配的内存(如main中的a r1)是存储在堆中的,堆是内存的一块单独区域,用来进行存储空间的分配。变量的地址表示哪儿能够寻找到存储的空间。表3-5表示了一个可能的变量赋值过程(在Linux i686平台上)。
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
![阅读 ‧ 电子书库](https://book.vik.im/contents/771570957898/images/00049.jpeg)
注意,这些变量的地址相差不远,意味着它们在同一个程序中。*main.ar1的地址是从属于堆的地址范围的。在传统的计算机图表中,栈是在内存中“向下增长”,而堆是“向上增长”,如图3-4所示。
![阅读 ‧ 电子书库](https://book.vik.im/contents/771570957898/images/00050.jpeg)
地址的变化揭示了计算机从处理main函数转移到处理f函数的过程,变量的地址是不断地向下增长的。如果栈太大的话,程序就会崩溃,因为独立栈帧的内存将会覆盖一些内存,而堆却在安全地保护着这些内存。这种交叉的发生与硬件平台以及为操作系统进程初始分配的内存有关。
在例3-2中,这个无限循环在第393 060次循环调用的时候,产生了一个“段错误”,这个时候执行栈的大小已经超过了12 577 888字节。
例3-2:无限循环的代码
![阅读 ‧ 电子书库](https://book.vik.im/contents/771570957898/images/00051.jpeg)
程序执行时不断地调用函数和从函数中返回,执行栈在不断地收缩。所有属于栈的内存都会被自动地回收。但是,属于堆的内存是在程序员的控制之下,必须显式地释放。在多数情况下,如果一个程序释放内存失败,它也许可以继续正常运行(当程序结束时,它使用的内存会被操作系统回收)。
一个程序可能超出堆的空间(虽然这是一个典型的严重缺陷的标志),如果堆变得过大,那么就存在和执行栈相互干涉的风险。考虑例3-3的程序,这个程序反复地请求分配内存来存储字符串。
例3-3:内存泄漏的代码
![阅读 ‧ 电子书库](https://book.vik.im/contents/771570957898/images/00052.jpeg)
malloc调用向内存分配系统请求一个固定大小的内存(单位是字节)。如果有足够的堆空间,分配的内存地址就会返回给程序员(否则返回空地址0x0)。在这个例子中,没有任何释放内存的操作,在经过了356 331 411次迭代之后,这个程序被手动地结束了[1]。每一次迭代分配26个字节内存,这个程序结束的时候大概分配了这台机器上16G内存中的15.9G。一旦内存不再需要(由程序员决定),那么就必须释放,内存空间会返回给堆以备将来之用。