一、动态内存分配与传统开辟内存的比较
我们一致的内存开辟方式有:
int val =20;//在栈空间上开辟4个字节。 char arr[10]={0};//在栈空间上开辟10个字节。
上述开辟空间的方式有两个特点:
1.空间开辟大小是固定的。
2.数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是有时空间的大小在运行时才能获知,那么上述开辟空间的方式就不能满足了,于是就产生了动态内存开辟。
二.动态内存函数
2.1 malloc 和 free
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,就返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是void*,所以malloc函数并不知道开辟空间的类型,具体使用的时候使用者自己决定强转哪种类型。
如果参数size为0,malloc的行为是标准未定义的,取决于编译器。
另外在使用malloc的时候,要和它的好搭档free搭配使用,free是专门用来做动态内存的释放和回收的。
void free (void* ptr);
free函数用来释放动态开辟的内存。
具体使用:
1.如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的。(错误的)
2.如果参数ptr是NULL,则函数无操作。
2.2 malloc和free具体使用
#include<stdio.h> #include<errno.h> int main() { int num; scanf("%d", &num); int* ptr = NULL; ptr = (int*)malloc(num * sizeof(int)); if (num == NULL)//判断是否为空 { printf("%s\n", strerror(errno)); } for (int i = 0; i < num; i++) { *(ptr + i) = 0; } free(ptr);//释放ptr所指向的动态内存 ptr = NULL; return 0; }
这里要申明两件事:
1.开辟空间后做检查是否为NULL指针是非常重要的。
2.注意误区:free动态开辟的内存后,该动态开辟的空间虽然被释放,但是地址仍旧存在,指针不为NULL,可以访问,但会出错,所以养成良好习惯,要置为NULL指针。(务必记住)
3.free函数只能释放动态开辟的内存或者空指针,释放其他内存会出错。
2.3 calloc
C语言还提供了一个函数叫calloc,calloc函数也用来动态内存分配,原型如下:
void* calloc(size_t num, size_t size);
1.函数的功能:为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0.
2.calloc函数与malloc的区别在于calloc会在返回地址之前把申请的空间的每个字节初始化为0.
例如:
所以如果要求我们对初始开辟的空间进行初始化时,我们可以很使用calloc函数很轻松解决这个问题。
2.4 realloc
realloc函数的出现让动态内存管理更加灵活。
有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小的调整。
函数原型:
void* realloc (void* ptr,size_t size);
函数解析:
· ptr是要调整的内存地址
· size 调整之后新大小
· 返回值为调整之后的内存起始位置
· 这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间。
realloc在调整内存空间时存在两种情况:
1.原有空间之后有足够大的空间。
对于情况一,要扩展内存就直接原有内存之后直接追加空间。
2.原有空间之后没有足够大的空间。
当是情况2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。
由于上述的两种情况,realloc函数的使用就要注意一些,我们通常会再创建一个变量来充当桥梁的作用用于过渡。
譬如:
int main() { int* ptr = (int*)calloc(10, sizeof(int)); if (NULL == p) { printf("%s\n", strerror(errno)); } int* p = (int*)realloc(ptr, 100); if (p != NULL) { ptr = p; p=NULL; } free(ptr); ptr = NULL; return 0; }
通过对上述函数的介绍,我们对于动态开辟内存有了一定的了解,简单总结一下需要注意:
1.开辟空间后要记住做检查是否为NULL指针。
2.注意free动态开辟的内存后,指针不为NULL,可以访问,但会出错,所以养成良好习惯,要置为NULL指针。(务必记住)
3.free函数只能释放动态开辟的内存或者空指针,释放其他内存会出错。
4.在使用realloc函数时要记住创建一个中间变量来过渡,使用过后,记得中间变量置为NULL空指针,防止有两个地址指向同一块空间,防止因为情况一空间不够导致开辟失败。
三、常见的动态内存错误
3.1 对NULL指针的解引用操作
这个问题也就是我在上面提到的,动态开辟后要进行检查,防止为NULL指针。
void test() { int* p = (int*)malloc(INT_MAX / 4); //要进行检查 *p = 20;//如果p的值是NULL,就会有问题 free(p); }
3.2 对动态开辟空间的越界访问
int main() { int* p = (int*)malloc(40); if (p == NULL) { printf("%s\n", strerror(errno)); return 1; } //访问 int i = 0; for (i = 0; i <= 10; i++)//越界访问 { p[i] = i; } free(p); p = NULL; return 0; }
这个问题比较常见,在常规开辟内存时也是应该注意的问题。
3.3 对非动态开辟内存使用free释放
void test() { int a = 10; int* p = &a; free(p);//ok? }
答案肯定是不ok的,free只能释放动态开辟的空间或者是NULL空指针(无操作).
3.4 使用free释放动态开辟内存的一部分
这个问题比较不容易发现,很多时候调试出现错误可能就是因为这里出了错。
大家可以看一下以下两组代码,例如:
int main() { int* p = (int*)malloc(40); if (p == NULL) { return 1; } //使用 int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } //释放 free(p);//仅释放一部分,不合理,应该释放指向起始位置的。 p = NULL; return 0; }
代码二:
int main() { int* p = (int*)malloc(40); if (p == NULL) { return 1; } //使用 int i = 0; for (i = 0; i < 10; i++) { *p = i; p++; } //释放 free(p);//仅释放一部分,不合理,应该释放指向起始位置的。 p = NULL; return 0; }
这两段代码的不同之处就在于,malloc开辟空间初始化的方式,在这里第一种不会出错,而第二种则会出错。
希望大家要注意++是有副作用的,这里第一个代码p指针所指向的地址始终是开辟空间的初始地址,而第二个代码p的地址被++给更改,这样free就会出错。