不花钱的网站怎么做,江门网站免费制作,专业网站建设公,网站建设硬件预算Hi~#xff01;这里是奋斗的小羊#xff0c;很荣幸您能阅读我的文章#xff0c;诚请评论指点#xff0c;欢迎欢迎 ~~ #x1f4a5;#x1f4a5;个人主页#xff1a;奋斗的小羊 #x1f4a5;#x1f4a5;所属专栏#xff1a;C语言 #x1f680;本系列文章为个人学习… 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 题目三2.4 题目四 3、柔性数组3.1 什么是柔性数组3.2 柔性数组的特点3.3 柔性数组的使用3.4 柔性数组的优势 总结 前言
总的来说动态内存管理为我们提供了更加灵活、高效和可扩展的内存管理方式但动态内存管理函数可能会带来一些风险主要包括内存泄漏、内存溢出和野指针等问题我们在使用动态内存管理函数时要多留心避免风险的出现 1、常见动态内存错误
1.1 对NULL指针的解引用操作
如果我们写的代码不严谨没有考虑到动态内存分配失败的可能就会写出类似于下面的代码
#include stdio.h
#include stdlib.hint 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.hint 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.hint main()
{int* p (int*)malloc(10 * sizeof(int));//判断p是否为空指针if (p NULL){//打印出错误信息perror(malloc);//终止程序return 1;}int i 0;//p1跳过1个整型p10就会越界for (i 0; i 10; i){p[i] i 1;}return 0;
}聪明的VS也会检测出错误提示你 1.3 对非动态开辟内存使用free释放
free函数是用来释放由动态内存函数开辟的空间的不能释放普通内存
#include stdio.h
#include stdlib.hint 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.hint 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 NULLreturn 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.hvoid 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.hvoid 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.hvoid 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.hvoid 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.hchar* 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.hchar* 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.hvoid 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.hvoid 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;
}2.4 题目四
请问运行下面 text函数会有什么后果
#include stdio.h
#include stdlib.h
#include string.hvoid 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;
}使用malloc函数申请一块100个字节大小的动态内存空间放入字符串“hello”然后使用free函数释放这一动态内存空间 但是此时指针str中还存着我们开辟的动态内存空间的地址正确的写法free函数后应紧跟str NULL;但是上面的代码并没有这一条语句 当if语句判断的时候指针str确实是不为空指针的进入if语句后执行strcpy(str, world);这条代码根据我们对strcpy函数的了解这里还要对指针str解引用但是指针str我们之前已经用free函数释放过了并且没有赋NULL所以str此时是野指针不能解引用运行起来程序就会出错
这道题考察的还是free函数后紧跟p NULL的问题 3、柔性数组
3.1 什么是柔性数组
C99中结构体中的最后一个成员允许是未知大小的数组这就叫柔性数组成员
在结构体中最后一个成员未知大小的数组
struct S1
{int n;char c;double d;int arr[];//未知大小的数组
};struct S2
{int n;char c;double d;int arr[0];//未知大小的数组
};上面两种写法中arr都是柔性数组成员 有些编译器可能只支持其中的一种写法VS中两种写法都支持 3.2 柔性数组的特点 结构中的柔性数组成员前面必须至少有一个其他成员sizeof返回的这种结构大小不包含柔性数组的内存包含柔性数组成员的结构用malloc函数进行内存的动态分配并且分配的内存应该大于结构的大小以适应柔性数组的预期大小 正是因为sizeof返回的这种结构大小不包含柔性数组的内存所以结构中的柔性数组成员前面必须至少有一个其他成员否则结构体的大小没法计算 3.3 柔性数组的使用
包含柔性数组的结构怎么使用呢 包含柔性数组的结构创建变量不会像一般结构那样创建而是使用malloc函数进行内存的动态分配
#include stdio.h
#include stdlib.hstruct S
{int n;int arr[];
};int main()
{struct S* ps (struct S*)malloc(sizeof(struct S) 20 * sizeof(int));if (ps NULL){perror(malloc);//终止程序return;}//使用空间ps-n 100;int i 0;for (i 0; i 20; i){ps-arr[i] i 1;}//...free(ps);ps NULL;return 0;
}柔性数组的柔性怎么体现呢 因为上面包含柔性数组的结构是由malloc函数进行内存的动态分配所以我们可以使用realloc函数进行动态内存的调整那这个数组的大小就可大可小
#include stdio.h
#include stdlib.hstruct S
{int n;int arr[];
};int main()
{struct S* ps (struct S*)malloc(sizeof(struct S) 20 * sizeof(int));if (ps NULL){perror(malloc);//终止程序return 1;}//使用空间ps-n 100;int i 0;for (i 0; i 20; i){ps-arr[i] i 1;}//调整ps指向的空间大小struct S* ptr (struct S*)realloc(ps, sizeof(struct S) 40 * sizeof(int));//进行指针的非空判断保护原地址if (ptr ! NULL){ps ptr;//防止ptr变成野指针ptr NULL;}else{perror(realloc);//终止程序return 1;}for (i 0; i 40; i){printf(%d , ps-arr[i]);}//...free(ps);ps NULL;return 0;
}如果不使用柔性数组还有一种办法能实现上面的效果
#include stdio.h
#include stdlib.hstruct S
{int n;int* arr;
};int main()
{struct S* ps (struct S*)malloc(sizeof(struct S));if (ps NULL){perror(malloc);return 1;}int* tmp (int*)malloc(20 * sizeof(int));if (tmp NULL){perror(malloc);return 1;}else{ps-arr tmp;tmp NULL;}ps-n 100;int i 0;//给指针arr指向的20个整型空间赋值for (i 0; i 20; i){ps-arr[i] i 1;}//调整指针arr指向的空间大小tmp (int*)realloc(ps-arr, 40 * sizeof(int));if (tmp ! NULL){ps-arr tmp;tmp NULL;}else{perror(realloc);return 1;}for (i 0; i 40; i){printf(%d , ps-arr[i]);}//...free(ps-arr);ps-arr NULL;free(ps);ps NULL;return 0;
}结构struct S中有一个指针成员我们的想法是用malloc函数申请一块动态内存空间再让结构中的这个指针指向这块动态分配的内存然后这块由指针指向的动态内存空间就可以用realloc函数进行大小的调整了 可以看到这样实现的效果和柔性数组相似那柔性数组为什么还要存在呢 其实相比之下柔性数组还是有它的优势的 3.4 柔性数组的优势
方便内存释放
如果我们的代码是在一个给别人用的函数中你在里面做了两次内存分配并把整个结构体返回给用户用户调佣free可以释放结构体但是用户并不知道这个结构体内的成员也需要free所以你不能指望用户来发现这个事 所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存释放
这样有利于访问速度
连续的内存有利于提高访问速度也有利于减少内存碎片 因为malloc等动态内存函数在申请空间时会在堆区允许的地方申请一块连续的空间但是动态内存函数申请的多个动态内存空间之间并不是连续的这些空间之间就形成了内存碎片 总结 动态内存管理是一把双刃剑它能给我们提供灵活的内存管理方式但同样也会带来风险检查动态内存分配是否成功在使用动态内存管理函数时应该检查分配内存是否成功以确保程序正常运行这是比较容易忽略的点