一、动态内存分配与释放
1、为什么要使用动态内存分配,以下看一个实例,关于超市中购买记录的一段程序
#include <stdio.h>
#include <string.h>
struct Product
{
char name[128];
int price;
};
struct Product pro[1000]; //1000有限制,所以要使用动态内存分配
struct Product sale;
void waitForSale()
{
printf("Record Product: ");
scanf("%s %d", sale.name, &sale.price);
}
int main()
{
int i = 0;
for(i=0; i<1000; i++)
{
waitForSale();
strcpy(pro[i].name, sale.name);
pro[i].price = sale.price;
printf("%s, %d\n", pro[i].name, pro[i].price);
}
return 0;
}
上面程序的改进方法:
1、定义1000个元素的数组。当元素个数超过1000时就要定义一个指针struct product *p = malloc();
2、一開始就使用malloc动态申请内存空间。
这两种方法都要用到动态内存的分配。
那为什么要动态分配内存?
1、C语言中的一切操作都是基于内存的
2、变量和数组都是内存的别名,怎样分配这些内存由编译器在编译期间决定
# 定义数组时必须指定数组长度
# 而数组长度是在编译期间就必须决定的
需求:
程序执行期间,可能须要使用一些额外的内存空间。
# malloc和free用于执行动态内存的分配和释放
系统中有一些内存空间是闲置下来的,它们是留给程序执行期间去动态申请的地址空间。
就是我们通常说的内存池,当程序执行期间须要动态申请一块地址空间的时间就使用malloc进行动态申请。使用完毕后就要使用free释放掉。
2、malloc和free
# malloc所分配的是一块连续的内存,以字节为单位,而且不带不论什么的类型信息
# free用于将动态内存归还系统
void *malloc(size_t size);
void free(void * pointer);
注意:
1、malloc实际分配的内存可能会比请求的略微多一点,可是不能依赖于编译器的这个行为。
也就是说我们不要利用这一点做文章。知道就能够了
2、当请求的动态内存无法满足时malloc返回NULL
3、当free的參数为NULL时,函数直接返回
3、calloc和realloc
# 你认识malloc的兄弟吗?
void * calloc(size_t num, size_t size);
void * realloc(void *pointer, size_t new_size);
# calloc的參数代表所返回内存的类型信息
# calloc会将返回的内存初始化为0
# realloc用于改动一个原先已经分配的内存块大小
1、在使用realloc之后应该使用其返回值
2、当pointer的第一个參数为NULL时,等价于malloc
实例分析:
#include <stdio.h>
#include <malloc.h>
int main()
{
int i = 0;
int* pI = (int*)malloc(5 * sizeof(int));
short* pS = (short*)calloc(5, sizeof(short)); //它带有类型信息。5个元素。每一个元素是short类型
for(i=0; i<5; i++)
{
printf("pI[%d] = %d, pS[%d] = %d\n", i, pI[i], i, pS[i]);
}
pI = (int*)realloc(pI, 10 * sizeof(int));
for(i=0; i<10; i++)
{
printf("pI[%d] = %d\n", i, pI[i]);
}
int *pp = (int *)malloc(0);
printf("%s\n", *pp);
free(pI);
free(pS);
return 0;
}
执行结果(注意观察结果)
pI[0] = 4064336, pS[0] = 0
pI[1] = 4064336, pS[1] = 0
pI[2] = 1380930387, pS[2] = 0
pI[3] = 1162103135, pS[3] = 0
pI[4] = 1179210830, pS[4] = 0
pI[0] = 4064336
pI[1] = 4064336
pI[2] = 1380930387
pI[3] = 1162103135
pI[4] = 1179210830
pI[5] = 1380930387
pI[6] = 1447382111
pI[7] = 909986885
pI[8] = 1330794496
pI[9] = 1397966147
(null)
小结:
1、动态内存分配是C语言中的强大功能
2、程序能够在须要的时候有机会使用很多其它的内存
3、malloc单纯的从系统中申请固定字节大小的内存
4、calloc能以类型大小为单位申请内存并初始化为0
5、realloc用于重置内存大小
malloc(0);将返回NULL;
二、程序中的三国天下
栈、堆、静态存储区
1、程序中的栈
# 栈是现代计算机程序里最为重要的概念之中的一个
# 栈在程序中用于维护函数调用上下文,没有函数,没有局部变量。
# 栈保存了一个函数调用所须要的维护信息
1、函数參数,函数返回地址
2、局部变量
3、函数调用上下文
所以我们不要返回局部变量的指针
2、程序中的堆
# 为什么有了栈还须要堆?
栈上的数据在函数返回后就会被释放掉。无法传递到函数外部,如:局部数组
# 堆是程序中一块巨大的内存空间。可有程序*使用
# 堆中被程序申请使用的内存在程序主动释放前将一直有效
栈与堆的一个重要差别就是栈是系统分配和释放,而堆必须通过申请才干获得。
#系统对堆空间的管理方式
空暇链表法,位图法,对象池发等等
由图能够看出。假设没有合适的内存块,就会返回NULL。
所以malloc(0)会返回NULL。
空暇链表是内核记录内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻址第一个空间大于所申请空间的堆结点,然后将该结点从空暇链表中删除。并将该结点的空间分配给程序。另外。对于大多数系统,会在这块内存空间的首地址处记录内存分配的大小。这样代码中的delete或free就能够正确的释放本内存空间。
另外由于找到的堆结点的大小不一定正好等于申请大小,系统会将多余的那部分又一次放入空暇链表中。
3、程序中的静态存储区
# 程序静态存储区随着程序的执行而分配空间,直到程序执行结束
# 在程序的编译期静态存储区的大小就已经确定
# 程序的静态存储区主要用于保存程序中的全局变量和静态变量
# 与栈和堆不用。静态存储区的信息终于会保存到可执行程序中
程序三国天下,分工明白
小结:
1、栈、堆和静态存储区是C语言程序涉及的三个基本内存区
2、栈区主要用于函数调用的使用
3、堆区主要是用于内存的动态申请和归还
4、静态存储区用于保存全局变量和静态变量
三、程序内存布局
1、程序文件的一般布局
# 代码在可执行程序中的相应关系
# 文件布局在内存中的映射(进程)
# 各个段的作用
1、堆栈段在程序执行后才正式存在。是程序执行的基础
2、.bss段存放的是未初始化的全局变量和静态变量,bss段并不给该段的数据分配空间。仅仅是记录数据所需空间大小
3、.text段存放的是程序中的可执行代码
4、.data段保存的是那些已经初始化了的全局变量和静态变量。data段已经为数据分配了空间。数据保存在目标文件里。
5、.rodata段存放程序中的常量值,如字符串常量,.rodata段的数据不能够改动,假设改动会出现segment error的错误
样例:
void main()
{
char *p = "hello, world\n";
p[0] = 'H';
printf(p);
}
程序术语相应关系
1、静态存储区通常指程序中的.bss和.data段
2、仅仅读区通常指程序中的.rodata段
3、局部变量所占空间为栈上空间
4、动态空间为堆中的空间
5、程序可执行代码存放于.text段
四、野指针和内存操作分析
1、初识野指针
# 野指针一般是由于指针变量中保存的值不是一个合法的内存地址而造成的
# 野指针不是NULL指针,是指向不可用内存的指针
# NULL指针不easy用错。由于if语句能非常好的推断一个指针是不是NULL
C语言中没有不论什么手段能够推断一个指针是否为野指针!
2、野指针的由来
1、局部指针变量没有被初始化
#include <stdio.h>
#include <string.h>
struct Student
{
char* name;
int number;
};
int main()
{
struct Student s;
strcpy(s.name, "Delphi Tang"); // s是局部变量,并没有初始化,s.name是一个随机值,也就是一个野指针
s.number = 99;
return 0;
}
2、使用已经释放过后的指针
#include <stdio.h>
#include <malloc.h>
#include <string.h>
void func(char* p)
{
printf("%s\n", p);
free(p);
}
int main()
{
char* s = (char*)malloc(5);
strcpy(s, "Delphi Tang"); //数组越界
func(s);
printf("%s\n", s); / 使用了已经释放的指针,后果可大可小。
//小的话。正好野指针没有被使用。大的话。有些程序会莫名其妙的关闭
return 0;
}
# 指针所指向的变量在指针之前被销毁
#include <stdio.h>
char* func()
{
char p[] = "Delphi Tang";
return p;
}
int main()
{
char* s = func();//s指向一块已经被释放的空间
printf("%s\n", s); //局部变量已经被释放了,这里打印结果是不可预料的。
return 0;
}
3、非法内存操作分析
# 结构体成员指针未初始化
# 没有为结构体指针分配足够的内存
#include <stdio.h>
#include <malloc.h>
struct Demo
{
int* p;
};
int main()
{
struct Demo d1;
struct Demo d2;
int i = 0;
for(i=0; i<10; i++)
{
d1.p[i] = 0; // p已经是野指针了
}
d2.p = (int*)calloc(5, sizeof(int));
for(i=0; i<10; i++)
{
d2.p[i] = i; // 超出申请空间,改写了申请空间后5个字节。会导致逻辑混乱
}
free(d2.p);
return 0;
}
# 内存分配成功。但并未初始化
#include <stdio.h>
#include <malloc.h>
int main()
{
char* s = (char*)malloc(10);
printf(s); // 内存未初始化
free(s);
return 0;
}
# 内存越界
#include <stdio.h>
void f(int a[10])
{
int i = 0;
for(i=0; i<10; i++)
{
a[i] = i; // 数组越界
printf("%d\n", a[i]);
}
}
int main()
{
int a[5];
f(a);
return 0;
}
# 内存泄露分析
#include <stdio.h>
#include <malloc.h>
void f(unsigned int size)
{
int* p = (int*)malloc(size*sizeof(int));
int i = 0;
if( size % 2 != 0 )
{
return; // 返回时没有释放堆空间,所以出现了内存泄露,单入口。多出口造成的问题。
}
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
f(9);
f(10);
return 0;
}
//改动为单入口,单出口。
void f(unsigned int size)
{
int* p = (int*)malloc(size*sizeof(int));
int i = 0;
if( size % 2 = 0 )
{
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
}
free(p);
}
# 多次指针释放
#include <stdio.h>
#include <malloc.h>
void f(int* p, int size)
{
int i = 0;
for(i=0; i<size; i++)
{
p[i] = i;
printf("%d\n", p[i]);
}
free(p); //不正常
}
//养成习惯。谁申请。谁释放。
避免bug。避免加班。
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
f(p, 5);
free(p); // 二次释放指针
return 0;
}
# 使用已释放的指针
#include <stdio.h>
#include <malloc.h>
void f(int* p, int size)
{
int i = 0;
for(i=0; i<size; i++)
{
printf("%d\n", p[i]);
}
free(p);
}
int main()
{
int* p = (int*)malloc(5 * sizeof(int));
int i = 0;
f(p, 5);
for(i=0; i<5; i++)
{
p[i] = i; // 使用已释放的指针
}
return 0;
}
5、C语言中的交通规则
# 用malloc申请了内存之后。应该马上检查指针值是否为NULL。防止使用值为NULL的指针
int *p = (int *)malloc(5 * sizeof(int));
if (p != NULL)
{
// do something here!
}
free(p);
#牢记数组的长度,防止数组越界操作。考虑使用柔性数组
typedef struct _soft_array
{
int len;
int array[];
}softArray;
int i = 0;
softArray *sa = (sottArrray *)malloc(sizeof(softArray) + sizeof(int) * 10);
sa - >len = 10;
for(i=0; i<sa->len; i++)
{
sa->array[i] = i + 1;
}
# 动态申请操作必须和释放操作匹配。防止内存泄露和多次释放
void f()
{
int *p = (int *)malloc(5);
free(p);
}
int main()
{
int *p = (int *)malloc(10);
f();
free(p);
return 0;
}
# free指针之后必须马上赋值为NULL
int *p = (int *)malloc(10);
free(p);
p = NULL;
//...
//...
if(p ! = NULL)
{
int i = 10;
for(i = 0; i < 5; i++)
{
p[i] = i;
}
}