专题五-内存管理的艺术

来源:互联网 发布:中越中美网络大战 编辑:程序博客网 时间:2024/06/05 06:55

动态内存分配

为什么使用动态内存分配?
(1)C语言中的一切操作都是基于内存的
(2)变量和数组都是内存的别名,如何分配这些内存由编译器在编译期间决定
.定义数组的时候必须制定数组的长度
.而数组长度在编译期就必须决定的 

需求:程序运行的过程中,可能需要使用一些额外的内存空间

malloc和free
(1)malloc所分配的是一块连续的内存,以字节为单位,并且不带任何的类型消息
(2)free用于将动态内存归还于系统:void* malloc( size_t,size);void free(void* pointer)

注意:(1)malloc实际分配的内存可能会比请求的稍微多一点,但是不能依赖于编译器的这个行为
   (2)当请求的动态内存无法满足时malloc返回NULL
   (3)当free的参数为NULL时,函数直接返回


calloc和realloc
(1)你认识malloc的兄弟吗? void* calloc(size_t size,size_t num),num为类型的大小,size为元素的数目
(2)calloc的参数代表所返回内存的类型信息
(3)calloc会将返回的内存初始化为0;malloc分配的值时随机的!!!!!
void* realloc(void* pointer,size_t new_size)
(4)realloc用于修改一个原先已经分配的内存块大小,新分配的值也随机!!!!!
在使用realloc之后应该使用其返回值
当pointer的第一个参数为NULL时,等价于malloc


实例分析:calloc和realloc的使用





小结:
(1)动态内存分配是C语言中的强大功能
(2)程序能够在需要的时候有机会使用更多的内存
(3)malloc单纯的从系统的中申请固定字节大小的内存
(4)calloc能以类型大小为单位申请内存并初始化为0
(5)realloc用于重置内存大小


问题:malloc(0)将返回什么?
首先来解释malloc(0)的问题,这个语法是对的,而且确实也分配了内存,但是内存空间是0,就是说返回给你的指针是不能用的,感觉奇怪吧?但是从操作系统的原理来解释就不奇怪了,这要涉及操作系统维护内存的方法来说了,在内存管理中,内存被分为2部分,栈和堆,栈有自己的机器指令,是一个先进后出的数据结构,我就在这里不再过多解释了,malloc分配的内存是堆内存,由于堆没有自己的机器指令,所以要有系统自己编写算法来管理这片内存,通常的做法是用链表,在每片被分配的内存前加个表头,里面存储了被分配内存的起始地址和大小,你的malloc返回的就是表头里的起始指针,这个地址是由一系列的算法得来了,通常不会为0,一旦分配成功,就返回一个有效的指针,对于分配0空间来说,算法已经算出可用内存的起始地址,但是你占用0空间,所以对那个指针操作就是错误的,操作系统一般不知道其终止地址,因为有占用大小就可以推出终止地址,还有就是即使分配0空间也要释放它,其实是释放的链表结点

还有,返回的指针是可用地址的起始地址,虽然你可以无限赋值,但是其实是错误的,因为可能有其他有用的数据在那一片区域,如果指针越界就会出现意想不到的事情,不懂的再问




程序中的三国天下

程序中的栈
(1)栈是现代计算机程序里最为重要的概念之一
(2)栈在程序中用于维护函数调用上下文,没有栈就没有函数,没有局部变量

(3)栈保存了一个函数调用所需的维护信息
   .函数参数,函数返回地址
   .局部变量
   .函数调用上下文














程序中的堆
(1)为什么有了栈还需要堆?栈上的数据在函数返回之后就会释放掉,无法传递到函数外部,如:局部数组
(2)堆是程序中一块巨大的内存空间,可由程序自由使用
(3)堆中被程序申请使用的内存在程序主动释放前将一直有效
malloc从堆中申请内存。对空间专门为了动态内存分配而产生的。
堆空间通过申请才能获得

(4)系统对堆空间的管理方式
.空间链表法,位图法,对象池法等等


4与5相近

表头之后指向12个字节,p就指向了5个字节,前面说的 申请的时候内存会多一点就是这个意思

free就是插入节点



 如果只使用不归还,只malloc不free会导致什么后果?导致这个链表中所有的空闲节点全部被用完,空闲链表的表头就会指向NULL



程序中的静态存储区
(1)程序静态存储区随着程序的运行而分配空间,直到程序运行结束
(2)在程序的编译期静态存储区的大小就已经确定
(3)程序的静态存储区主要用于保存程序中的全局变量和静态变量
(4)与栈和堆不同,静态存储区的信息最终会保存到可执行程序中




小结
(1)栈,堆和静态存储区是C语言程序常涉及的三个基本存储区 
(2)栈区主要用于函数调用的使用
(3)堆区主要用于内存的动态申请和归还
(4)静态存储区用于保存全局变量和静态变量



 






程序的内存布局

  程序文件的一般布局


文件布局在内存中的映射


为什么在前面找不到栈和堆?因为栈和堆是要等到程勋运行之后操作系统分配他们的空间的

各个段的作用
(1)堆栈段在程序运行之后才正式存在,是程序运行的基础
(2).bss段存放的是未初始化的全局变量和静态变量的,全都为0
(3).text存放的是程序中的可执行代码
(4).data段存放的是那些已经初始化了的 全局变量和静态变量
(5).rodata段存程序中的常量值,如字符常量


这样的字符串肯定不会再栈上分配空间,因为我们main函数建立的活动记录里面只可能有指针变量p的空间,不会有helloworld的空间,他放在一个段里面rodata。
[cpp] view plain copy
 print?
  1.  1 #include<stdio.h>  
  2.   
  3.  2   
  4.   
  5.  3 int main()  
  6.   
  7.  4 {  
  8.   
  9.  5         char a[] = "hello world1";  
  10.   
  11.  6         char *p = "hello world2";  
  12.   
  13.  7         *(a+1)='q';  
  14.   
  15.  8         *(p+1)='q';  
  16.   
  17.  9         printf("%s\n",a);  
  18.   
  19. 10         printf("%s\n",p);  
  20.   
  21. 11   
  22.   
  23. 12 }  


运行结果

段错误。

注释 *(p+1)='q';

运行结果

hqllo world1

hello world2

原因:

 

一、a与p类型不同:p为指向字符串的指针;a为保存字符串的数组。

5 char a[] = "hello world1";   是个赋初值的字符数组。
6 char *p = "hello world2";  是个字符串常量指针;

指针变量p在栈里面

字符串常量"hello world2"在全局数据区,数据段,只读,不可写


二、"hello world2""hello world1"字符串保存的位置不同。"hello world1"保存在栈
    中,可用*(a+1)='q'修改,"hello world2"保存在全局数据
    区,位置是在.rodata中,不能修改*(p+1)='q'




程序术语对应关系
(1)静态存储区通常指程序中的.bss和.data段
(2)只读区通常指程序中的.rodata段
(3)局部变量所占空间为栈上空间
(4)动态空间为堆中空间
(5)程序的可执行代码存放于.text段
问题: 函数的地址对应程序的哪一个段?应该是我们进程空间里面程序的内存空间里面存放代码段的某个地址。



同是全局变量和静态变量,为什么初始化和未初始化的保存在不同段中呢??????
C规定,未初始化变量的初值为0,这个清0的操作是由启动代码完成的,还有已初始化变量的初值的设置,也是由启动代码完成的。
为了启动代码的简单化,编译链接器会把已初始化的变量放在同一个段:.data,这个段的映像(包含了各个变量的初值)保存在“只读数据段”,这样启动代码就可以简单地复制这个映像到 .data 段,所有的已初始化变量就都初始化了。
而未初始化变量也放在同一个段:.bss,启动代码简单地调用 memset 就可以把所有未初始化变量都清0。




头疼的野指针

初始野指针
(1)野指针通常是因为指针变量中保存的值不是一个合法的内存地址而造成的
(2)野指针不是NULL指针,是指向不可用内存的指针
(3)NULL指针不容易用错,因为NULL语句和好判断一个指针是不是NULL

C语言没有任何手段可以判断一个指针是否是野指针


野指针的由来
(1)局部指针变量没有被初始化


(2)使用已经释放过后的指针


(3)指针所指向的变量在指针之前被销毁





经典错误,你犯了吗?

非法内存操作分析
(1)结构体成员指针未初始化
(2)没有为结构体指针分配足够的内存


d1中的p是野指针,p没有分配动态内存空间,p指向的是随机的
d2中的p是分配呢5个int,但是分配10个,这种bug很难查

内存初始化分析
(1)内存分配成功,但未被初始化


内存越界分析
(1)数组越界


内存泄漏分析

设计程序最好要单入口单出口

多次释放指针

谁申请谁释放

使用已经释放的指针








交通规则,还是应该遵守
C语言中的交通规则
(1)用malloc申请了内存之后,应该立即检查指针值是否为NULL,防止使用值为NULL的指针


(2)牢记数组长度,防止数组越界操作,考虑使用柔性数组


(3)动态申请操作必须和释放操作匹配,防止内存泄漏和多次释放


(4)free指针之后必须立即赋值为NULL

0 0
原创粉丝点击