动态内存分配
为什莫要动态内存分配?
已掌握的空间开辟都是静态的,无法实现需要多少,开辟多少.这里动态开辟能跟加满足我们的需求。
动态内存开辟的函数 malloc calloc realloc free.
我们这里先说free,在动态内存扩容完成后,使用后就需要对该内存free.
free函数
free 释放空间,参数为空间的地址
函数类型为无返回的函数,参数为指针。功能是释放掉动态开辟的空间,因为动态开辟的空间都是在堆区开辟的,使用完后,应立马释放掉,并初始化指针为零,否则会形成野指针。而free函数就是专门用来释放掉开辟的空间。
功能,开辟内存块。在内存申请size_t大小的空间,并返回这个起始空间,类型为void *,储存在堆区中。
单位是字节。参数为size_t的大小类型。
int main() { int *p=(int *)malloc(20);//申请20个字节的空间 这里的类型整形比较合适 //空间申请失败(比如申请的太大)会返回一个空指针,且无法使用。 //若参数为0,无大小,malloc的行为是未定义的,取决于编译器。 if(p==NULL) { printf("%s\n", strerror(errno)); return 1; } //使用 //按照数组的形式访问使用 for (int i = 0; i < 5; i++) { *(p + i) = i + 1;//存放整形数据0,1,2,3,4. //p[0]=0;p[1]=1;...... } //malloc开辟的空间存放的是随机值 //释放动态内存开辟的空间 free(p); // free 后指针不会置空,此时p为一个野指针 还需要为空。 p = NULL; return 0; }
calloc 函数
也是开辟空间,开辟之后且用0来初始化,共有num个元素,元素大小为size_t,返回一个起始地址只想给内存块。参数为元素大小与元素空间大小,返回值为扩容后的地址空间。
int main() { int* p = (int*)calloc(10, sizeof(int)); return 0; //使用 int i = 0; for (i = 0; i < 10; i++) { printf("%d", p[i]);//打印1 2 3 4 5 6 7 8 9 10 return 1; } //释放 free(p); p = NULL; return 0; }
malloc与calloc区别
1.参数不一样
2.功能有所差异,都是在堆区上申请空间,但是malloc不初始化,calloc初始化,根据所需使用。
realloc函数
realloc 开启的空间更加灵活,
参数为指针与扩充的空间大小 。函数返回类型为一个指向新扩容地址空间。函数功能是改变在指针的空间大小。
int main() { int* p = (int*)mmalloc(20); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } //使用 int i = 0; for (i = 0; i < 5; i++) { p[i + 1] = i +1; } //可能返回空指针 //这里创建临时指针来存放 //假设空间不够用 int * ptr =(int *) realloc(p, 40); if (ptr != NULL) { p = ptr; } else { printf("realloc:%s\n", strerror(errno));//解释错误 } //使用 int i = 0; for (i=0; i < 10; i++) { p[i+1] = i + 1; for (int i = 0; i < 10; i++) { printf("%d \n", p[i]); } } //释放 free(p); p = NULL; return 0; }
假设空间不够用
开辟的空间可能是连续的,后接旧地址开辟空间。返回的是就指针。
也有可能是另外开辟的
若该地方不够,则会重开辟更大一块空间,把原来的数据重新拷贝到新空间 , 返回新空间的地址
使用完之后,释放旧的空间,如图表示:
常见动态内存错误
1.对NULL指针的解引用操作
int main() { int* p = (int*)malloc(20); int i = 0; //可能会出现对NULL指针的操作 //所以要对指针判断是否为空 for (i = 0; i < 5; i++) { p[i] = i;//这里会报错 } free(p); p = NULL; return 0; }
若扩容失败返回的是一个空指针并且未判断,则后对其解引用操作会出问题。
2.对动态内存开辟的空间越界访问
int main() { int* p = (int*)malloc(10); int i = 0; if(p == NULL) { return 1; } //越界访问 for (i = 0; i < 5; i++) { p[i] = i; } free(p); p = NULL; return 0; }
这里开辟的空间不够足够储存,只有10个字节,却要存放五个整形数据,则这里就是越界访问。程序崩溃,未响应。
3.对非动态内存开辟的空间free 不再堆区
int main() { int arr[10] = { 0 }; int* p = arr; free(p); p = NULL; //程序崩溃 }
不在堆区中的空间,违法free.free作用的对象是动态开辟的内存空间。
4.使用free释放的空间的一部分
int main() { int* p = (int*)malloc(40); int i = 0; for (i = 0; i < 5; i++) { //p[i] = i + 1;正确使用 *p = i + 1; //1 2 3 4 5 p++; } //释放 free(p);//。这里只释放了前五个整形的空间后,后面的20个字节 p = NULL;//程序崩溃 return 0; }
5.对同一块空间多次释放
int main() { int* p = (int*)malloc(20); if (p == NULL) { return 1; } //使用之后 free(p); //释放 //若置空 则在此时程序不会崩溃 p=NULL; free(p); p = NULL; }
这里注意。只要指针置空,则再次释放也可以。
6.动态内存忘记释放
void test() { int* p = (int*)malloc(20); if (*p = NULL) { return; } //使用 //假设用来存放了数据 } int main() { test(); return 0; } //一直吃内存,但不释放。电脑就会卡死。
程序正常结束时,若没使用free释放,则会被程序回收
但没结束,也没释放,则会内存泄漏。
动态实现通讯录
上次是实现的静态通讯录,更改为动态实现,这里主要说几个变更的。
这里的结构创建,采用结构体指针指向成员的空间代替静态数组,增加表示空间容量的capacity
typedef struct peoInfol { char name[20]; int age; char sex[5]; char telep[12]; char addr[30]; }peoInfo; typedef struct contact { peoInfo *data;//指向成员空间的指针 int size;//有效个数 int capacity;//空间大小能存放的个数 }Contact, * pContact;
新增宏定义
#define MAX 100 #define MAX_NAME 20 #define MAX_SIZE 5 #define MAX_ADDR 5 #define DEFAULT_SZ 3 //默认三个成员 #define INC_SZ 2 //每次扩容扩两个
初始化函数
void Initcontact(Contact* pc) { pc->data = (peoInfo*)malloc(DEFAULT_SZ * sizeof(peoInfo)); if (pc->data == NULL) { printf("通讯录初始失败:%s\n",strerror(errno)); return; }//初始化函数 pc->size = 0; pc->capacity = 0; }
扩容函数
void checkcapacity(Contact* pc,int sizeincrease) { if(pc->size == pc->capacity) { peoInfo* ptr = (peoInfo*)realloc(pc->data,( pc->capacity+ INC_SZ) * sizeof(peoInfo)); if (ptr == NULL) { printf("扩容失败:%s",strerror(errno)); return; } pc->data = ptr; pc->capacity + INC_SZ; printf("当前的容量大小为%d", pc->capacity); } }
释放空间
void Distorycon(Contact *pc) { free(pc->data); pc->data = NULL; pc->capacity= 0; pc->size = 0; printf("已释放空间........"); }