内存管理

简述:

  • :存放程序的局部变量和函数调用的参数返回地址。栈是一个向下增长的数据类型,具有后进先出(LIFO)的特性。栈上的内存由编译器自动分配和释放需要程序员干预栈上内存空间通常有限,因此不适合存放大量或复制的数据。
  • :存放程序动态分配的内存,由程序员控制其生命周期。堆是一个向上增长的数据结构,可以根据需要扩展或收缩。使用newdelete操作符分配和释放堆上的内存时,需要注意内存泄漏野指针等问题。delete之后要将地址设置为nullptr
  • 数据区:存放程序的全局变量和静态变量,分为初始化和未初始化两部分。初始化部分包含了程序赋予初始化的变量,未初始化部分包含了程序没有赋予初始化的变量。
  • 代码区:存放程序的可执行指令,通常是只读的,可以被多个进程共享

堆空间大小可以扩展或收缩空间大小有限

堆空间的访问速度比空间堆空间也容易产生内存碎片和内存泄漏

堆空间的地址增长是向上的,沿着内存地址增加的方向,而栈空间向下的,也就是沿着内存地址减小的方向增长

C语言内存管理

代码区

存放程序编译后的可执行二进制代码CPU执行的机器指令,并且是只读的

只读

共享每次打开exe文件,都会指向一个内存空间

全局/静态区

全局变量和静态变量的存储是放在一起的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放

  • 全局/静态区存储全局变量,静态变量,常量,该区变量在程序运行期间一直存在
  • 程序结束由系统回收
  • 已初始化的数据放在data段,未初始化的数据放到bss段
  • 该去变量当未初始化时,会有默认值初始化

堆区

开发者手动申请,手动释放,若不手动释放,程序结束后由系统回收,声明周期是整个程序运行期间,使用malloc进行堆内存申请,堆的总大小机器的虚拟内存的大小

  • 堆区由开发人员手动申请与释放,在释放之前,该块堆空间可一直使用
  • 由程序员分配和释放,若程序员不释放,程序结束时由系统回收内存
  • 堆空间一般没有软限制,只受限于硬件,会比栈空间更大,适宜存放较大数据

被调函数分配内存

void allocateSpace(char * pp)
{ 
	char * temp = malloc(100);  //temp为局部变量
	memset(temp, 0, 100);
	strcpy(temp, "hello world"); //helloworld对方到堆区
	pp = temp;  //传入的参数和pp的地址并不相同,pp作为参数存放在栈中,函数结束释放内存
}
//1.利用高级指针
void allocateSpac2(char ** pp) ////使用二级指针,此时pp的地址与传入参数地址相同
{
	char * temp = malloc(100);
	memset(temp, 0, 100);
	strcpy(temp, "hello world");
	*pp = temp; //使用调用的指针来
	printf("aaa%s\n", *pp);
}

栈区

编译器自动分配释放,存放函数的参数值,局部变量的值

  • 栈是一种先进后出的内存结构,由编译器自动分配释放数据
  • 主要存放函数的形式参数值局部变量
  • 函数运行结束,相应栈变量会被自动释放
  • 空间较小不适合大量数据存放在栈中

注意:不要返回局部变量的地址,在调用函数之后,局部变量就已经被释放,a的地址被销毁,在对指针p进行访问,就属于非法访问内存

程序指令和程序数据分开原因

  • 程序被加载到内存中之后,可以将数据和代码分别映射到两个内存区域。由于数据区域对进程来说是可读可写的,而指令区域对程序来讲是只读的,所以分区之后,可以将程序指令区域和数据区域分别设置成只读,这样就可以防止程序有意或无意被修改
  • 当系统中运行着多个同样的程序时,这些程序指令都是一样的,所以只需要在内存中保存一份程序指令即可,只是每一个程序运行中数据不一样而已,这样可以节省大量的内存

内存分配

宏定义

  • 宏定义和宏常量都是利用#define定义出来的内容
  • 在项目中,经常把一些短小而又频繁使用的函数写成宏函数
  • 宏函数没有普通函数参数压栈跳转返回等时间上的开销

注意:宏函数要加括号,保证运算的完整,宏定义仅负责替换,不复制运行等相关操作。

可以把频繁使用并且短小的函数,写成宏函数,宏函数在编译阶段就替换源码,没有普通函数入栈出栈的开销,以空间换时间

参考资料