一、内存泄露memory leak
由于疏忽或错误造成程序未能释放已经不再使用内存的情况。即用完了动态申请的内存后没有归还,导致自己也无法使用申请的内存(地址弄丢),系统也不能再次将它分配给程序。一次内存泄露危害可以忽略,但是内存堆积后果很严重。
1.内存泄露可以分为四类:
常发性内存泄露:发生内存泄露的代码会被多次执行到,每次被执行的时候都会导致一块内存泄露。
偶发性内存泄露:发生内存泄露的代码只有在某些特定环境或操作过程下才会发生。对于特定的环境,偶发性的也许就变成了常发性的。
一次性内存泄露:发生内存泄露的代码只会被执行一次,或者由于算法上的缺陷,导致总会有一块仅且一块内存发生泄露。比如构造函数中分配内存,析构函数中没有释放内存。
隐式内存泄露:程序在运行过程中不停的分配内存,但是直到结束的时候才释放内存。严格的说这里并没有发生内存泄露,因为最终程序释放了所有申请的内存,但是对于一个服务器程序,运行时间几天,不及时释放内存也可能导致最终耗尽系统的所有内存。
2.主要有两种类型的内存泄露
堆内存泄露(heap leak):对内存指的是程序运行中根据需要分配通过malloc、realloc、new等从堆中分配的一块内存,如果没有使用free和delete释放这块内存,那么这块内存将不会被使用,就会产生head leak。
系统资源泄露(resource leak):指程序使用系统分配的资源比如bitmap、handle、socket等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。
二、内存溢出(out of memory)
程序要求分配的内存超出了系统能提供的,系统不能满足要求就会产生内存溢出。内存泄露最终会导致内存溢出。
常见的溢出问题主要有:
内存未分配成功却使用了它。解决方法是检查指针是否为NULL。若指针p是函数参数,在函数入口处用assert(p!=NULL)检查;若为动态申请内存,用if(p==NULL)进行防错处理。
内存虽然分配成功,没有初始化就使用(野指针)。
内存分配成功并初始化,但是操作越界。(for循环)
使用free和delete释放内存后,没有将指针设置为NULL,产生野指针。
三、内存对齐
1.同类的静态数据成员一样,对结构体时,如果结构体中有静态变量,那么一定要为它初始化,并且只能在结构体外初始化。
为什么在类外或者结构体外初始化?因为在没有实例化结构体时,并不为类或者结构体分配空间,所以只能在类外初始化。
为什么一定要对静态数据定义初始化?因为静态数据是属于整个类和结构体,和对象无关。如果不初始化,那么每次定义一个该类/结构体对象时,每个对象都有自己的一个静态数据成员,显然这是不对的。
2.1)运行结果中的char类型的乱码原因:程序把&ch当做一个字符串的首址。
<<运算符重载时,若参数为char*,cout就输出字符串。Ch是char,则&ch为char*,所以输出&ch的字符串。例如:
这里pc为char*,则输出pc时输出pc中存放的字符串。
如果想输出地址,可以在地址前加(void*)或者(int*)。
2)在为变量分配内存时,编译器会对内存进行优化,即不按照定义的先后顺序分配内存。
3)static在静态存储区存储,因此地址和之前的不连续。
4)结构体/类的大小不包含静态变量的大小。如左图,不管有没有去掉static,其sizeof都是20.
由于CPU一次读取若干个字节的内存数据,所以为方便存取,编译器可以指定结构体的对齐方式。
3.内存对齐的原因
由于CPU从内存中读取数据是以“块”为单位的,块长可为1,2,4,8等。为了减少读取次数,提高效率,那么就需要内存对齐。
内存对齐的规则
1)结构体中的第一个成员的首地址就是结构体变量的首地址;
2)结构体中的每个成员的首地址相对于结构体的首地址的偏移量是该成员数据类型大小的整数倍。
3)数据成员完成各自对齐之后,结构体本身也要对齐。结构体的总大小是对齐模数的整数倍。
注:对齐模数是#pragma pack(n)所指定的n和结构体中最大数据类型的成员大小的最小值。可以通过#pragma pack(n)来更改操作系统规定的对齐模数。