前言
个人笔记
一、程序内存布局
应用程序运行在一个虚拟内存空间里,32位的系统,寻址空间为232 = 4G,最大只能支持4G内存;而64位的系统,寻址空间为264 ,理论上可以支持很大的内存。大部分操作系统都将内存空间的一部分分给内核调用,应用程序不能直接访问这段内存,这一部分内核地址成为内核态空间。这里以32位系统为例,Linux默认将高地址的1G空间分配给内核作为内核态空间,剩下的3G空间作为用户态空间,而用户态一般有以下4个默认区域:
- 1、栈区:由编译器自动分配释放,存放函数的参数值、局部变量的值等。操作方式类似于数据结构中的栈。
- 2、堆区:一般由程序员分配释放,它与数据结构中的堆是两个概念,分配方式类似于链表。
- 3、读写段:全局变量与静态变量的存放在同一块区域(读写段),在程序编译时分配。
- 4、只读段:
- 4.1、文字常量区:一般存放常量字符串,在只读段的高地址。
- 4.2、程序代码区:存放函数体的二进制代码,比如类的成员函数、全局函数,在只读段的低地址。
代码如下(示例):
#include <iostream>
int a;//全局区,默认会赋初值等于0
char *p;//全局区,默认是null
int main()
{
int b;//栈区,默认随机值
char *p1;//栈区,该指针会乱指一通(野指针)
static int c = 30;//全局静态区
const char *pstr = "hello,world";//pstr本身位于栈区,指向的值位于文字常量区
int *p3 = new int(40);//p3本身位于栈区,指向的变量位于堆区
printf("\n打印变量的地址\n");
printf("&a = %p\n", &a);
printf("&p = %p\n", &p);
printf("p = %p\n", p);
printf("&b = %p\n", &b);
printf("&p1 = %p\n", &p1);
printf("p1 = %p\n", p1);
printf("&c = %p\n", &c);
printf("&pstr = %p\n", &pstr);
printf("pstr = %p\n", pstr);
printf("hello,world = %p\n", &"hello,world");
printf("&p3 = %p\n", &p3);
printf("p3 = %p\n", p3);
printf("&main = %p\n", &main);//程序代码区
printf("main = %p\n", main);//程序代码区
printf("\n打印变量的值\n");
printf("a = %d\n", a);
printf("b = %d\n", b);
delete p3;
p3 = nullptr;
return 0;
}
执行一个非内置的程序时,加载器会将可执行目标文件中的代码和数据从磁盘拷贝到内存中。每个Linux程序都会有一个运行时存储器映像,虚拟内存空间如图所示。
- 1、在32位系统下,代码段总是从地址0x08048000处开始。
- 2、数据段是在接下来的下一个4KB对齐的地址处。
- 3、运行时堆(heap)在读写段之后接下来的第一个4KB对齐的地址。
二、栈与堆的比较
栈与堆有以下几个方面不同:
- 1、栈由编译器自动管理,无需手动控制,速度快;堆需要程序员控制,速度慢,容易产生内存泄漏(memory leak)。
- 2、栈的内存大小一般较小,而堆内存几乎没有什么限制。
- 3、对于栈来说,分配方向是朝着内存地址减小的方向;对于堆来,分配方向是朝着内存地址增加的方向。
- 4、对于堆来说,频繁的new/delete会导致内存空间不连续,造成大量的碎片,使得程序效率降低;对于栈来说,则不会存在这个问题。
- 5、堆都是动态分配,由malloc、calloc等函数进行分配;而栈的动态分配是由编译器进行分配释放,无需手动实现;静态分配由编译器完成,比如局部变量的分配,