赞
踩
目录
我们已经掌握的内存开辟方式有:
- int val = 20; //在栈空间上开辟4个字节
- char arr[10] = { 0 }; //在栈空间上开辟10个字节的连续空间
上述开辟空间的方式有两个特点:
但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道,那数组编译时开辟空间的方式就不能满足了。这时候就只能试试动态内存开辟了。
C语言库函数提供了两个函数:malloc和free,分别用于执行动态内存分配和释放。这些函数维护一个可用内存池。当一个程序另外需要一些内存时,它就调用malloc函数,malloc从内存池中提取一块合适的内存,并向该函数返回一个指向这块内存的指针。这块内存此时并没有以任何方式进行初始化。如果对这块内存进行初始化非常重要,你要么自己动手对它进行初始化,要么使用calloc函数。当一块以前分配的内存不再使用时,程序调用free函数把它归还给内存池供以后之需。
函数原型:void* malloc(size_t size);
头文件:#include<stdlib.h>
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
函数原型:void free(void* ptr);
头文件:#include<stdlib.h>
free函数用来释放动态开辟的内存。
free函数的实际参数必须是先前由内存分配函数返回的指针。(参数也可以是空指针,此时free调用不起作用。)如果参数是指向其他对象(比如变量或数组元素)的空指针,可能会导致未定义的行为。虽然free函数允许收回不再需要的内存,但是使用此函数会导致一个新的问题:悬空指针。 调用free(p)函数会释放p指向的内存块,但是不会改变p本身。如果忘记了p不再指向有效内存块,混乱可能随即而来。试图访问或修改释放掉的内存块会导致未定义的行为。试图修改释放掉的内存块可能会引起程序崩溃等损失惨重的后果。
在C语言中,指针测试真假的方法和数的测试一样。所有非空指针都为真,而只有空指针为假。因此,语句if (p == NULL)可以写成if(!p),if (p != NULL)可以写成if(p)。
案例一:
- int main()
- {
- //申请空间
- int* ptr = (int*)malloc(40);
-
- if (ptr == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- *(ptr + i) = i;//ptr的指向并没有发生变化
- }
-
- //释放空间
- free(ptr);
- ptr = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
在调用完free函数之后,如果不加上ptr==NULL,虽然free(ptr)函数会释放ptr指向的内存块,但是不会改变ptr本身。这就将导致一个新问题:悬空指针。
为了避免这个问题的发生,我们可以在free(ptr)
之后将其置为NULL即可。
不加ptr==NULL释放前:
不加ptr==NULL释放后:
加ptr==NULL释放后:
案例二:
- int main()
- {
- //申请空间
- int* ptr = (int*)malloc(40);
- int* p = ptr;
-
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- //使用
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- *p = i;
- p++;//p的指向发生变化
- }
-
- //释放空间
- free(ptr);
- ptr = NULL;
-
- if (ptr != NULL)
- {
- *ptr = 100;
- }
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
案例三:失败案例
- int main()
- {
- int* ptr = (int*)malloc(40);
- int* p = ptr;
-
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- free(p);
- p=NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
free函数的实际参数必须是先前由内存分配函数返回的指针ptr。如果参数是指向其他对象(比如变量或数组元素)的空指针,可能会导致未定义的行为。
总结:
函数原型:void* calloc(size_t num, size_t size);
头文件:#include<stdlib.h>
calloc函数为num个元素的数组分配内存空间,其中每个元素的长度都是size个字节。如果要求的空间无效,那么此函数返回空指针。在分配了内存之后,calloc函数会通过把所有位设置为0的方式进行初始化。
malloc和calloc之间的主要区别是后者在返回指向内存的指针之前把它初始化为0。calloc和malloc之间另一个较小的区别是它们请求内存数量的方式不同。calloc的参数包括所需元素的数量和每个元素的字节数。
案例:
- int main()
- {
- //malloc申请的空间不会被初始化为0
- //int* p = (int*)malloc(40);
- //申请10个整型的空间
-
- //calloc申请的空间会被初始化为0
- int* p = calloc(10, sizeof(int));//10为元素的个数,sizeof(int)为元素的大小
-
- if (p == NULL)
- {
- perror("calloc");
- return 1;
- }
-
- //使用
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- printf("%d ",*(p+i));
- }
-
- free(p);
- p = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
调试分析:
函数原型:void* realloc(void* ptr, size_t size);
头文件:#include<stdlib.h>
当调用realloc函数时,ptr必须指向先前通过malloc,calloc或realloc的调用获得的内存块。size表示内存块的新尺寸,新尺寸可能会大于或小于原有尺寸。
realloc函数用于修改一个原先已经分配的内存块的大小。使用这个函数,你可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原来的的内容依然保留,新增的内容添加到原先内存块的后面,新内存并未以任何方法进行初始化。如果它用于缩小一个内存块,该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然保留。
如果原先的内存块无法改变大小,realloc将分配另一块正确的大小,并把原来那块内存的内容复制到新的块上。因此,在使用realloc之后,你就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。
最后,如果realloc函数的第1个参数是NULL,那么它的行为就和malloc一模一样。如果realloc函数被调用时以0作为第二个实际参数,那么它会释放掉内存块。
案例:
- int main()
- {
- int* p = (int*)malloc(40);
-
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- *(p + i) = i;
- }
-
- //空间不够,希望能放20个元素,考虑扩容
- int* ptr = realloc(p, 80);
-
- if (ptr != NULL)
- {
- p = ptr;
- }
-
- //扩容成功,开始使用
- for (i = 10; i < 20; i++)
- {
- *(p + i) = i;
- }
-
- //不再使用,释放
- free(p);
- p = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
调试分析:
1.首先malloc分配40位的空间,也就是10个int型大小:
2.然后进行扩容,该扩容是在原空间的基础上进行的:
3.扩容成功后,则开始使用该空间:
4.使用完之后,则开始释放空间:
小结:
如何接收realloc的返回值?如果用旧地址去接收,那么当realloc找不到合适的空间进行内存分配时,这时realloc的返回值将变成NULL。此时不仅空间没有开辟好,原有空间的内容也可能丢失。那么我们应该打印错误信息然后结束程序,不要再往下执行。所以建议先用新地址接收,如果开辟成功再把它赋值给旧地址,这样就可以避免原有内存块数据的丢失。
案例:
- int main()
- {
- int* p = (int*)malloc(1000);
-
- int i = 0;
- for (i = 0; i < 250; i++)
- {
- *(p + i) = i;
- }
-
- free(p);
- p=NULL;
-
- return 0;
- }
分析:
malloc的返回值可能为NULL,此时将NULL赋值给指针p,则p的值也为NULL。然而后面又对p进行解引用,也就是对指针NULL进行解引用,此时的编译器将会发生警告。
解决办法:对malloc函数的返回值进行判断
改正:
- int main()
- {
- int* p = (int*)malloc(1000);
-
- //对p进行判空操作
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- for (i = 0; i < 250; i++)
- {
- *(p + i) = i;
- }
-
- free(p);
- p=NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
案例:
- int main()
- {
- int* p = (int*)malloc(1000);
-
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- //可能越界访问
- for (i = 0; i <= 250; i++)
- {
- *(p + i) = i;
- }
-
- free(p);
- p = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
i <= 250这个条件判断错误,数组产生越界,i的值应该小于250。
解决办法:对内存边界要检查
改正:
- int main()
- {
- int* p = (int*)malloc(1000);
-
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- //可能越界访问
- for (i = 0; i < 250; i++)
- {
- *(p + i) = i;
- }
-
- free(p);
- p = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
案例:
- int main()
- {
- int a = 10;
- int* p = &a;
-
- free(p);
- p = NULL;
-
- return 0;
- }
分析:
free只能释放由malloc,calloc和realloc所开辟的内存空间,这些空间都是在堆区上进行开辟的。而变量a是在栈区开辟的,因此并不能让free来释放。
案例:
- int main()
- {
- int* p = (int*)malloc(1000);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- *p = i;
- p++;
- }
-
- //释放空间
- free(p);
- p = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
随着p++的执行,指针p的位置已经发生了变化,不再指向所开辟内存空间的起始位置,而是指向中间的某个位置。而free又是从所开辟内存空间的起始位置开始释放的,所以free(p)用在这里显然是不合适的。
案例:
- int main()
- {
- int* p = (int*)malloc(1000);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- free(p);
-
- //...
-
- free(p);
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
第一次free(p)时,此时所开辟的内存空间将会被释放,但是不会改变p本身,它还保存着之前所开辟内存空间的起始地址。所以我们要及时将p置为NULL,才能避免多次释放所带来的问题。
改正:
- int main()
- {
- int* p = (int*)malloc(1000);
- if (p == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- free(p);
- p = NULL;
-
- //...
-
- free(p);//当参数为NULL时,此时free调用不起作用
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
案例:
- void test()
- {
- int* p = (int*)malloc(100);
- if (NULL != p)
- {
- *p = 20;
- }
- }
-
- int main()
- {
- test();
- while (1)
- {
- ;
- }
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
p是一个局部变量,本来存放的是malloc所开辟内存空间的起始地址,但是当test函数调用完之后,变量p将会被释放。所以后面就没人再记得之前所开辟内存的起始地址,这将会导致内存泄漏。忘记释放不再使用的动态开辟的空间会造成内存泄漏。切记:动态开辟的空间一定要释放,并且正确释放。
改正:
- void test()
- {
- int* p = (int*)malloc(100);
-
- if (NULL != p)
- {
- *p = 20;
- }
-
- free(p);
- p = NULL;
- }
-
- int main()
- {
- test();
- while (1)
- {
- ;
- }
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
当动态分配的内存不再需要使用时,它应该被释放,这样它以后可以被重新分配使用。分配内存但在使用完毕后不释放将引起内存泄漏。在那些所有执行程序共享一个通用内存池的操作系统中,内存泄漏将一点点地榨干可用内存,最终使其一无所有。要摆脱这个困境,只能重启系统。
- void GetMemory(char* p)
- {
- p = (char*)malloc(100);
- }
-
- void Test(void)
- {
- char* str = NULL;
- GetMemory(str);
- strcpy(str, "hello world\n");
- printf(str);
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
改正:
方案一:
- void GetMemory(char** p)
- {
- *p = (char*)malloc(100);
- }
-
- void Test()
- {
- char* str = NULL;
- GetMemory(&str);
-
- strcpy(str, "hello world");
-
- printf(str);
-
- free(str);
- str = NULL;
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
方案二:
- char* GetMemory(char* p)
- {
- p = (char*)malloc(100);
- return p;
- }
-
- void Test(void)
- {
- char* str = NULL;
- str = GetMemory(str);
- strcpy(str, "hello world");
- printf(str);
- free(str);
- str = NULL;
- }
-
- int main02()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- char* GetMemory(void)
- {
- char p[] = "hello world";
- return p;
- }
-
- void Test(void)
- {
- char* str = NULL;
- str = GetMemory();
- printf(str);
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
运行结果:
分析:
改正:
- char* GetMemory(void)
- {
- char* p = "hello world";
- return p;
- }
-
- void Test(void)
- {
- char* str = NULL;
- str = GetMemory();
- printf(str);
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- void GetMemory(char** p, int num)
- {
- *p = (char*)malloc(num);
- }
-
- void Test(void)
- {
- char* str = NULL;
- GetMemory(&str, 100);
- strcpy(str, "hello");
- printf(str);
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
在调用GetMemory()函数时,由malloc函数开辟的内存空间在使用完后并没有及时地使用free进行释放,因而造成了内存泄漏。
改正:
- void GetMemory(char** p, int num)
- {
- *p = (char*)malloc(num);
- }
-
- void Test(void)
- {
- char* str = NULL;
- GetMemory(&str,100);
- strcpy(str,"hello world");
- printf(str);
-
- free(str);
- str = NULL;
- }
-
- int main()
- {
- Test();
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
- void Test(void)
- {
- char* str = (char*)malloc(100);
- strcpy(str, "hello");
- free(str);
-
- if (str != NULL)
- {
- strcpy(str, "world");
- printf(str);
- }
- }
-
- int main()
- {
- Test();
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
分析:
在使用free释放由malloc所开辟的空间,虽然所开辟的空间已被释放,但是str依旧保存着所开辟空间的起始地址,这将导致指针悬空。如果忘记str不再指向有效内存块,此时刻试图访问或修改释放掉的内存块会导致未定义的行为。试图修改释放掉的内存块可能会引起程序崩溃等损失惨重的后果。
改正:
- void Test(void)
- {
- char* str = (char*)malloc(100);
- strcpy(str,"hello");
- free(str);
-
- //指针置为空
- str = NULL;
-
- if (str != NULL)
- {
- strcpy(str," world");
- printf(str);
- }
- }
-
- int main()
- {
- Test();
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
规定:
C99中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员,但结构中的柔性数组成员前面必须至少有一个其他成员。
例如:
- struct S
- {
- int num;
- double d;
- int arr[0];//柔性数组成员
- };
有些编译器会报错无法编译,可以改成:
- struct S
- {
- int num;
- double d;
- int arr[];//柔性数组成员
- };
1.结构中的柔性数组成员前面必须至少一个其他成员;
2.sizeof返回的这种结构大小不包括柔性数组的内存;
- struct S3
- {
- int num;
- int arr[0];//柔性数组成员
- };
-
- int main()
- {
- printf("%d\n",sizeof(struct S3));//4
-
- return 0;
- }
3.包含柔性数组成员的结构用malloc()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
柔性数组可以结合malloc进行使用,用以动态开辟内存空间;同时也可以结合realloc进行使用,用以内存空间的扩容。
案例:
- struct S3
- {
- int num;
- int arr[0];//柔性数组成员
- };
-
- int main()
- {
- printf("%d\n",sizeof(struct S3));//4
-
- //开辟空间
- struct S3* ps = (struct S3*)malloc(sizeof(struct S3) + 40);//40用于柔性数组成员
-
- //判空
- if (ps == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- //使用
- ps->num = 100;
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- ps->arr[i] = i;
- }
-
- //打印
- for (i = 0; i < 10; i++)
- {
- printf("%d ",ps->arr[i]);
- }
-
- //扩容
- struct S3* ptr = (struct S3*)realloc(ps, sizeof(struct S3) + 80);
-
- //判空
- if (ptr == NULL)
- {
- perror("realloc");
- return 1;
- }
- else
- {
- ps = ptr;
- }
-
- //使用
- for (i = 10; i < 20; i++)
- {
- ps->arr[i] = i;
- }
-
- //继续打印
- for (i = 10; i < 20; i++)
- {
- printf("%d ", ps->arr[i]);
- }
-
-
- //释放
- free(ps);
- ps = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
前面我们讲到可以使用柔性数组来动态开辟内存空间,这里我们将其改写成使用动态数组来开辟内存空间。示例如下:
- struct S4
- {
- int num;
- int* arr;
- };
-
- int main()
- {
- //开辟空间
- struct S4* ps = (struct S4*)malloc(sizeof(struct S4));
-
- //判空
- if (ps == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- //为动态数组开辟空间
- ps->arr = (int*)malloc(40);
-
- //判空
- if (ps->arr == NULL)
- {
- perror("malloc");
- return 1;
- }
-
- //使用
- ps->num = 100;
- int i = 0;
- for (i = 0; i < 10; i++)
- {
- ps->arr[i] = i;
- }
-
- //打印
- for (i = 0; i < 10; i++)
- {
- printf("%d ", ps->arr[i]);
- }
-
- //扩容
- int* ptr = (int*)realloc(ps->arr, 20 * sizeof(int));
-
- //判空
- if (ptr == NULL)
- {
- perror("realloc");
- return 1;
- }
- else
- {
- ps->arr = ptr;
- }
-
- //使用
- for (i = 10; i < 20; i++)
- {
- ps->arr[i] = i;
- }
-
- //继续打印
- for (i = 10; i < 20; i++)
- {
- printf("%d ", ps->arr[i]);
- }
-
-
- //释放
- free(ps->arr);
- ps->arr = NULL;
-
- //释放
- free(ps);
- ps = NULL;
-
- return 0;
- }
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
可以发现,改用动态数组的方式同样可以达到与使用柔性数组一样的效果。那二者有何差异呢?
通过对比可以发现:
malloc
和一次free
;malloc
和两次free
;一.方便内存释放:
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。
二.有利于访问速度:
连续的内存有益于提高访问速度,也有益于减少内存碎片。(其实,也没多高,反正你跑不了要用做偏移量的加法来寻址。)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。