Hi~!这里是奋斗的小羊,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~
💥💥个人主页:奋斗的小羊
💥💥所属专栏:C语言
🚀本系列文章为个人学习笔记,在这里撰写成文一为巩固知识,二为展示我的学习过程及理解。文笔、排版拙劣,望见谅。
目录
- 1、常见动态内存错误
- 1.1 对NULL指针的解引用操作
- 1.2 对动态内存空间的越界访问
- 1.3 对非动态开辟内存使用free释放
- 1.4 使用free释放动态内存的一部分
- 1.5 对同一快动态内存多次释放
- 1.6 动态开辟内存忘记释放(内存泄漏)
- 2、动态内存经典笔试题分析
- 2.1 题目一
- 2.2 题目二
- 2.3 题目三
1、常见动态内存错误
1.1 对NULL指针的解引用操作
如果我们写的代码不严谨,没有考虑到动态内存分配失败的可能,就会写出类似于下面的代码:
#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(10 * sizeof(int));//直接使用指针pint i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}return 0;
}
这样的代码可能并没有什么问题,但是存在很大的隐患,因为动态内存函数是有可能开辟内存空间失败的,当开辟失败时会返回NULL
,而NULL
指针是不能解引用的
像VS这样比较强大的编译器会立马检测到并提示你
为了避免这种错误,我们需要对指针p进行判断,再决定是否使用
#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(10 * sizeof(int));//判断p是否为空指针if (p == NULL){//打印出错误信息perror("malloc");//终止程序return 1;}int i = 0;for (i = 0; i < 10; i++){p[i] = i + 1;}return 0;
}
1.2 对动态内存空间的越界访问
我们用动态内存函数开辟多大的空间,我们就使用多大的空间,不能越界访问,例如:
#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(10 * sizeof(int));//判断p是否为空指针if (p == NULL){//打印出错误信息perror("malloc");//终止程序return 1;}int i = 0;//p+1跳过1个整型,p+10就会越界for (i = 0; i <= 10; i++){p[i] = i + 1;}return 0;
}
聪明的VS也会检测出错误提示你
1.3 对非动态开辟内存使用free释放
free
函数是用来释放由动态内存函数开辟的空间的,不能释放普通内存
#include <stdio.h>
#include <stdlib.h>int main()
{int arr[10] = { 0 };int* p = arr;free(p);p = NULL;return 0;
}
当我们运行起来后就出问题了
1.4 使用free释放动态内存的一部分
上面我们用malloc
函数申请了10个整型空间,然后通过for
循环给这10个整型空间内放1~10的整数,有些同学可能会为了方便这样写代码:
#include <stdio.h>
#include <stdlib.h>int main()
{int* p = (int*)malloc(10 * sizeof(int));//判断p是否为空指针if (p == NULL){//打印出错误信息perror("malloc");//终止程序return 1;}//给申请的动态空间内存1~10int i = 0;for (i = 0; i < 5; i++){*p++ = i;}//释放动态内存空间free(p);p = NULL;return 0;
}
当我们运行起来才发现写出了BUG
这又是为什么呢?
事实上此时free(p)
中的p指针已经不再指向malloc
开辟的动态内存的起始地址了,因为*p++
这里对p的指向不断递增
free
操作的指针必须指向要被释放的动态内存的起始地址
1.5 对同一快动态内存多次释放
当我们用完一块动态内存空间后不再使用对其释放后,可能会因为忘记而重复释放一次,并且如果第一次释放时忘记给p指针赋NULL
,那么程序就会出错
//使用...//释放动态空间free(p);//...free(p);p = NULL;return 0;
但是如果我们两次释放时都给p指针赋了NULL
,那基本不会发生什么事,相当于没有错,只是逻辑上讲不通
所以,在我们用free
释放完动态内存空间后,紧跟着对指针赋NULL
是很有必要的
1.6 动态开辟内存忘记释放(内存泄漏)
动态开辟的空间一定要释放,并且正确释放
当我们写代码的时候,存在这样一种可能会出现的错误,那就是动态开辟的内存忘记释放或者因为某些原因还没有到free
语句就提前终止代码,这里举个简单的例子
#include <stdio.h>
#include <stdlib.h>void text()
{int flag = 1;int* p = (int*)malloc(100);if (p == NULL){return 1;}//使用//因为某些原因函数提前返回了if (flag == 1){return;}//free函数free(p);p = NULL;
}int main()
{//自定义函数text();//后面还有大量代码//....return 0;
}
虽然我们确实用了free
函数释放空间,但是当代码量较大时可能会因为某些原因还没到free
函数就提前终止了,而我们还没意识到,就算后面我们意识到了这个问题这块内存我们也找不到了
只有整个程序结束后这块内存才能被释放,如果程序一直不结束这块空间就再也找不到了,这就叫内存泄漏
所以,就算动态内存申请使用后用了
free
,也是有可能犯内存泄漏的错误,我们要多加小心
内存泄漏是比较可怕的,尤其是某些24小时不断运行的服务器程序,如果存在内存泄漏,内存被耗干也只是时间的问题
2、动态内存经典笔试题分析
2.1 题目一
请问运行下面 text
函数会有什么后果?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void get_memory(char* p)
{p = (char*)malloc(100);
}void text(void)
{char* str = NULL;get_memory(str);strcpy(str, "hello world");printf(str);
}int main()
{text();return 0;
}
上面的代码一共有两个问题
第一个问题:malloc
申请动态内存空间后没有使用free
函数释放,这可能会导致内存泄漏
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void get_memory(char* p)
{p = (char*)malloc(100);
}void text(void)
{char* str = NULL;get_memory(str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}int main()
{text();return 0;
}
第二个问题: 函数传参传值调用和传址调用使用错误
这个代码的意思是申请一块动态内存空间地址交给指针p,通过指针p再交给指针str,再使用strcpy
函数将字符串拷贝到动态内存空间内,最后打印出字符串
但是get_memory
函数传参的时候使用的是传值调用,所以指针p跟指针str没有关系
有两种纠错方法
方法一: 将传值调用改为传址调用,此时p为二级指针
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void get_memory(char** p)
{*p = (char*)malloc(100);
}void text(void)
{char* str = NULL;get_memory(&str);strcpy(str, "hello world");printf(str);free(str);str = NULL;
}int main()
{text();return 0;
}
方法二: 直接返回指针p的地址,不需要传参
char* get_memory()
{char* p = (char*)malloc(100);return p;
}void text(void)
{char* str = NULL;str = get_memory();strcpy(str, "hello world");printf(str);free(str);str = NULL;
}int main()
{text();return 0;
}
2.2 题目二
请问运行下面 text
函数会有什么后果?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>char* get_memory(void)
{char p[] = "hello world";return p;
}void text(void)
{char* str = NULL;str = get_memory();printf(str);
}int main()
{text();return 0;
}
上面的代码是一个非常经典的例子,之前在C语言(指针)3中野指针一小节介绍过类似的例子
上面代码的问题:
我们在自定义函数get_memory
中创建了一个局部临时数组存入字符串“hello world”,再将字符串的首地址返回用指针str接收,虽然此时指针str确实指向字符串“hello world”的首地址,但是此时str是没有权限访问这块空间的
因为在局部数组p在出了get_memory
函数后就销毁了,它申请的空间会被收回,即使指针str能找到这块空间,但是它已经没有权限使用了,此时str就是一个野指针
所以我们应该避免返回栈空间地址
想要改正上面的代码也很简单,我们申请一块动态内存就行,同时也别忘了释放
#include <stdio.h>
#include <stdlib.h>
#include <string.h>char* get_memory(void)
{char* p = (char*)malloc(20);strcpy(p, "hello world");return p;
}void text(void)
{char* str = NULL;str = get_memory();printf(str);free(str);str = NULL;
}int main()
{text();return 0;
}
2.3 题目三
请问运行下面 text
函数会有什么后果?
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void get_memory(char** p, size_t num)
{*p = (char*)malloc(num);
}void test(void)
{char* str = NULL;get_memory(&str, 100);strcpy(str, "hello world");printf(str);
}int main()
{test();return 0;
}
上面的代码是可以打印出“hello world”的,但是遗憾的是上面的代码中使用了动态内存函数malloc
,但是没有使用free
函数释放动态内存空间
虽然上面的代码可以实现我们想要的效果,但这样的代码是存在安全隐患的
动态内存开辟函数
malloc
、calloc
、realloc
和动态内存释放函数free
必须成对出现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void get_memory(char** p, size_t num)
{*p = (char*)malloc(num);
}void test(void)
{char* str = NULL;get_memory(&str, 100);strcpy(str, "hello");printf(str);free(str);str = NULL;
}int main()
{test();return 0;
}