动态内存分配(有关动态内存分配的题目和常见的错误)
一、首先我们先看一下(常见的动态内存的错误)
1.直接对NULL的解引用操作
代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(40); int i = 0; for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } free(p); p = NULL; return 0; }
(1.)这个就是一个错误写法,因为此时没有进行判断malloc是否开辟内存成功,假如失败,此时这个指针就是一个空指针,所以下面对空指针进行解引用,就是一个非法访问的操作,所以这是一个错误的写法
(2.)所以我们在进行开辟动态空间的时候就一定要对其进行判断,只有这样才可以避免这个问题
(3.)正确写法如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(40); int i = 0; if (p != NULL) { for (i = 0; i < 10; i++) { *(p + i) = i; } for (i = 0; i < 10; i++) { printf("%d ", *(p + i)); } } free(p); p = NULL; return 0; }
2.对动态开辟的内存的越界访问
(1.)代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(5 * sizeof(int)); if (p == NULL) { return 0; } else { int i = 0; for (i = 0; i < 10; i++)//越界访问(因为我只开辟了5个整形的空间,而现在却使用了10个整形的空间,所以就会造成非法访问空间) { *(p + i) = i; } } free(p); p = NULL; return 0; }
(1.)越界访问(因为我只开辟了5个整形的空间,而现在在循环中却循环了10个整形的空间,所以就会造成非法访问空间)
(2.)所以在使用动态内存空间的时候就一定要注意我开辟的内存到底有多大
(3.)正确写法如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(5 * sizeof(int)); if (p == NULL) { return 0; } else { int i = 0; for (i = 0; i < 5; i++) { *(p + i) = i; } } free(p); p = NULL; return 0; }
3.对非动态开辟内存使用free释放
(1.)这个就非常好理解(就是free的作用只能是释放开辟的内存)
(2.)错误代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> void test(int*p) { *p = 20; free(p); p = NULL; } int main() { int a = 10; test(&a); printf("%d", a); return 0; }
(3.)因为此时这个a变量创建是在栈区上进行的,而我的free释放的动态内存是在堆区上进行,所以我不能用free去释放栈区的空间,我只能释放堆区的空间,所以这个写法也是错误的
4.使用free释放一块动态内存开辟内存的一部分
(1.)先看代码:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(40); if (p != NULL) { int i = 0; for (i = 0; i < 10; i++) { *p++ = i;//小知识点后置++,先使用后加加 //*(p+i)=i; } } return 0; }
(2.)主要的错误就是在*p++ = i;虽然此时可以和 (p+i)=i;起到相同的作用,但是意思却完全不一样 了,因为如果写成p++ = i;此时就会使我的指针的位置一直向后移动(指针向后移动后才可以把我的 i 的数据放进去 ),所以就导致了p指针指向的位置发生了改变(从本来指向首元素的位置变到了指向存放数据9的那个地址处),所以此时的p指针指向的地址就再也不是我原来向堆区申请的那个空间了,所以如果此时进行free(释放内存),就会导致释放的内存不是我原来开辟的完整的内存,只是一部分而已,所以我释放内存时,就会释放一些未知的内存,所以又会导致内存的非法访问
(3.)所以当我们在使用动态内存,就一定不能去改变此时这个指针指向的地址(使用时就应该对其进行解引用后再去使用)
(4.)代码修改如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(40); if (p != NULL) { int i = 0; for (i = 0; i < 10; i++) { *(p+i)=i;//对其进行解引用使用(用下标完成任务) } } return 0; }
5.对同一块动态内存的多次释放
(1.)这个好理解,代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> int main() { int* p = (int*)malloc(40); if (p != NULL) { //…… free(p); free(p); } return 0; }
2.)就是对同一个动态内存进行多次释放,这个也是错误的
(3.)避免的好办法就是谁开辟谁释放和每一次进行动态内存的释放(就在后面加上一句p = NULL)
这样也可以防止多次释放(因为free空指针并不影响)
6.动态开辟内存忘记释放(导致内存的泄露)
(1.)代码如下:】
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> #include<windows.h> int main() { while(1) { malloc(10); Sleep(1000);//睡眠函数,睡眠1000毫秒的意思,就是1秒(头文件 #include<windows.h>) } return 0; }
(2.)这样一直死循环的进行动态开辟内存,然后没有进行内存的释放,就会导致我的内存一直减少,直到没有内存可以使用
(3.)所以只要进行了动态开辟内存就一定要进行free (否则内存泄露是非常吓人的)
(4.)修改如下:
#include<stdlib.h> #include<string.h> #include<stdio.h> #include<windows.h> int main() { while(1) { int* p = (int*)malloc(10); Sleep(1000); } free(p) p=NULL; return 0; }
二、接下来我们介绍一下几个经典的有关动态内存开辟的题目(详解坑人处)
第一题:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> void GetMemory(char* p) { p = (char*)malloc(40); } void test(void) { char* str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } int main() { test(); return 0; }
讲解:
(1.)首先最明显的错误是如上面进行错误介绍所说,这边没有进行动态开辟内存的释放(所以有内存泄露的问题)
(2.)第二个错误就是因为:当我在一个函数中开辟空间时并且我传参的方式为传值,此时函数中的参数只是一份临时拷贝,使其出了这个函数的范围时,这个参数就失效了,所以我在GetMemory这个函数中开辟的40个字节的空间并不会改变我函数外部的任何参数,所以此时的内存开辟是一个失败的开辟,所以此时我的str指针并没有空间,此时还是一个空指针,所以此时我把"hello world"拷贝到一个空指正中,就导致非法访问内存造成了程序的失败
(3.)举一个例子:好比此时我开辟的40个字节的空间就是一个卧底,而我的p指针就是卧底的上司,只有上司知道卧底的身份,然而后来上司死了(也就是出了函数的范围),此时就没有人知道卧底的身份了,所以就会出问题
(4.)所以在函数中独立开辟空间时,一定要注意函数的传参形式(不敢用传值调用)
(5.)接下来顺便让我们理解一下printf(str);这个写法的意思
代码如下:
int main() { char* str = "abcdef"; printf("%s\n",str); printf("abcdef\n"); printf(str);//意思此时我把abcdef的地址放在了str这个指针变量中,所以此时str就是abcdef的地址,所以直接打印就行 return 0; }
(6.)这3中写法的意思是一模一样的
输出结果如图:
(7.)所以有时要关注好指针的用法
(8.)第一种修改方式如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> void GetMemory(char** p)//注意这个二级指针 { *p = (char*)malloc(40);//注意这个解引用 } void test(void) { char* str = NULL; GetMemory(&str);//注意这个传参 strcpy(str, "Hello World"); printf(str);//注意这个打印 free(str);//注意释放空间 str = NULL; } int main() { test(); return 0; }
(9.)修改了两处:
1.加上了free释放了内存
2.用传址的方式进行传参(但是要注意str原来的类型就是char*,所以接收时应该要用一个char**的二级指针来接收,并且要对p进行解引用,获得它的地址,这样才可以把我动态开辟的内存给放到这个地址处,不敢就写一个p不进行解引用,这样写仅代表的是一个指针,指针是不能接受空间的,所以一定要进行解引用)
(10.)第二种修改方式:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> char* GetMemory(char* p) { p = (char*)malloc(40); return p;//返回一个p给下面的代码,但是注意p的类型是char*,所以我的返回类型也要写char* } void test(void) { char* str = NULL; str = GetMemory(str);//并且这边刚好用str来接收这个返回值(意思就是把开辟的空间赋给我的空指针str),所以此时str就是一个指向了40个字节空间的指针了 strcpy(str, "Hello World"); printf(str);//这个鬼的理解写在下面 free(str); str = NULL; } int main() { test(); return 0; }
(11.)这种修改方式就是出函数时可以把开辟的内存的地址返回给我,此时我再用我的str空指针去接收
(12.)所以方法千千万,就看你牛不牛啦!
第二题:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> char* GetMemory(void) { char p[] = "Hello World"; return p; } void test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { test(); return 0; }
(1.)这题的关键就是你怎样去理解 Get Memory这个函数,和char p[ ] = “Hello World”;这个字符串数组的创建
(2.)首先这边肯定是没有内存泄露的(因为我根本就没有向堆区申请空间),没有动态开辟内存
(3.)char p[ ] = “Hello World”;首先可以看出这是一个局部数组变量的创建,这个创建根据以前的知识可以知道是在栈区上创建的,所以我在函数内部返回一个栈区上的地址(只要一出函数),这个地址中的内容就会销毁,所以此时这个栈区上的空间里面并没有放我想要的数据,返会的地址只是一个内存中随机的空间,并无实际作用,所以此时我用str这个空指针去接收我的这个地址,就是一个典型的(非法访问未知空间的操作),所以此时打印str时,就没有人会知道这个地址中到底存放的是什么东西
(4.)打印结果如下:
(5.)完美佐证我们上述的理解
(6.)所以如果我们想要进行改正,我们就要引入一个(static)的概念,把这个变量的生命周期延长,使其出了函数外部也不会被销毁(不会被销毁的原因在于,此时我是把这个变量放在了我内存中的静态区,就不是栈区了,所以就不存在返回栈区空间的情况,所以此时不会销毁,可以正常使用)
(7.)改正后的代码如下:
#define _CRT_SECURE_NO_WARNINGS #include<stdlib.h> #include<string.h> #include<stdio.h> char* GetMemory(void) { static char p[] = "Hello World"; return p; } void test(void) { char* str = NULL; str = GetMemory(); printf(str); } int main() { test(); return 0; }
(8.)加上这个static之后这个代码就是正确的了(生命周期变长)
(9.)所以只要我不返回栈区上的空间就行(所以可以返回静态区和堆区上的空间),前提是堆区上的空间没有被free(释放)
第三题:
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; }
(1.)这个的问题就只有一个就是没有进行内存释放(导致内存泄露的问题)
(2.)改正如下:
void GetMemory(char** p, int num) { *p = (char*)malloc(num); } void test(void) { char* str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); free(str);//加上这个释放就行 str=NULL; } int main() { test(); return 0; }
第四题:
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; }
(1.)首先这个代码最后是输出world
(2.)原因就是我释放我创建的动态内存是,释放后str此时并不是一个NULL指针,而是指向一个随机位置的指针,所以 if 语句的判断成功,所以进去打印world
(3.)然后问题依然是会非法访问内存(因为此时我已经把我的动态开辟的内存给释放掉的,所以此时这个str指针指向的那个地址,还是一个内存中的位置内存,所以此时你想把world放到str指针指向的地址中,此时你就造成了非法访问内存中的未知位置的空间)
(4.)改正代码如下:
void test(void) { char* str = (char*)malloc(100); strcpy(str, "hello"); free(str); str=NULL;//这边把str赋成空指针就行,这样此时它就不会再指向原来那个空间的地址,然后对str操作时,就不会再影响原来那个空间了 if (str != NULL) { strcpy(str, "world"); printf(str); } } int main() { test(); return 0; }
(5.)str=NULL; 这边把str赋成空指针就行,这样此时它就不会再指向原来那个空间的地址,然后对str操作时,就不会再影响原来那个空间了
三、所以我们这边再用一幅图来理解一下什么是内存空间
1.这幅图就能很好的说明各种数据类型在内存中是如何储存的