vs2008不能新建网站,wordpress有商城吗,网站定位有哪些,图片编辑软件✨✨ 欢迎大家来到贝蒂大讲堂✨✨ #x1f388;#x1f388;养成好习惯#xff0c;先赞后看哦~#x1f388;#x1f388; 所属专栏#xff1a;C语言学习 贝蒂的主页#xff1a;Betty‘s blog 1. 静态开辟内存
通过前面的学习#xff0c;我们已经掌握了两种开辟内存的方… ✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 养成好习惯先赞后看哦~ 所属专栏C语言学习 贝蒂的主页Betty‘s blog 1. 静态开辟内存
通过前面的学习我们已经掌握了两种开辟内存的方法分别是
#includestdio.h
int main()
{int val 20; //在栈空间上开辟四个字节char arr[10] { 0 }; //在栈空间上开辟10个字节的连续空间return 0;
}但是静态开辟的空间明显有两个缺陷 空间开辟⼤⼩是固定的。数组在申明的时候必须指定数组的⻓度数组空间⼀旦确定了⼤⼩不能调整。 2. 动态内存
为了解决静态内存开辟的内存空间固定的问题C语言引⼊了动态内存开辟让程序员⾃⼰可以申请和释放空间就⽐较灵活了。
2.1 动态内存开辟函数
(1) malloc函数 头文件#include stdlib.h 声明void* malloc (size_t size); size – 内存块的大小以字节为单位如果参数 size 为0malloc的⾏为是标准是未定义的取决于编译器。 作用向内存申请⼀块连续可⽤的空间并返回指向这块空间的指针 如果开辟成功则返回⼀个指向开辟好空间的指针。如果开辟失败则返回⼀个 NULL 指针因此malloc的返回值⼀定要做检查。 返回值返回值的类型是 void* 所以malloc函数并不知道开辟空间的类型具体在使⽤的时候使⽤者⾃⼰来决定。 补充打印错误信息函数perror() 头文件#include stdio.h 声明void perror(const char *str) str – 这是 C 字符串包含了一个自定义消息将显示在原本的错误消息之前。 作用把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str后跟一个冒号然后是一个空格。 返回值无返回值。 下列是malloc与perror的具体使用方法
int main()
{int* arr (int*)malloc(sizeof(int) * 10);//开辟十个大小为整型的空间//返回类型强转为int*if (arr NULL)//如果开辟失败{perror(malloc fail: );//打印错误信息return 1;//直接返回}int i 0;for (i 0; i 10; i)//存入数据{arr[i] i;}for (i 0; i 10; i)//打印数据{printf(%d , arr[i]);}return 0;
}输出结果 监视观察 动态内存的数据存放在堆区
(2) calloc函数 头文件#include stdlib.h 声明void *calloc(size_t nitems, size_t size) nitems – 要被分配的元素个数。size – 元素的大小。 作用 分配所需的内存空间并返回一个指向它的指针 返回值该函数返回一个指针指向已分配的内存。如果请求失败则返回 NULL。 malloc 和 calloc 之间的不同点是malloc 不会设置内存为零而 calloc 会设置分配的内存为零。
下列是calloc的使用实例
int main()
{int* arr (int*)calloc(10, sizeof(int));//开辟十个大小为整型的空间//返回类型强转为int*if (arr NULL)//如果开辟失败{perror(calloc fail: );//打印错误信息return 1;//直接返回}return 0;
}calloc的初始化观察 (3) realloc函数 头文件#include stdlib.h声明void *realloc(void *ptr, size_t size) ptr – 指针指向一个要重新分配内存的内存块该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果为空指针则会分配一个新的内存块且函数返回一个指向它的指针。size – 内存块的新的大小以字节为单位。如果大小为 0且 ptr 指向一个已存在的内存块则 ptr 所指向的内存块会被释放并返回一个空指针。 作用尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。返回值该函数返回一个指针 指向重新分配大小的内存。如果请求失败则返回 NULL。 有时会我们发现过去申请的空间太⼩了有时候我们⼜会觉得申请的空间过⼤了那为了合理的时候内存我们⼀定会对内存的⼤⼩做灵活的调整。那 realloc 函数就可以做到对动态开辟内存⼤⼩的调整。realloc扩容机制
本地扩容原有空间之后有⾜够⼤的空间直接在原有内存之后直接追加空间原来空间的数据不发⽣变化。 异地扩容原有空间之后没有⾜够⼤的空间在堆空间上另找⼀个合适⼤⼩的连续空间。将新增数据与原本数据拷贝过来并自动释放原来空间。 下列是realloc的具体使用方法
int main()
{int* arr (int*)calloc(10, sizeof(int));//开辟十个大小为整型的空间//返回类型强转为int*if (arr NULL)//如果开辟失败{perror(calloc fail: );//打印错误信息return 1;//直接返回}//继续新增空间int* tmp (int*)realloc(arr, sizeof(int) * 15);//不用arr是为了防止开辟失败被至为NULLif (tmp NULL)//如果开辟失败{perror(calloc fail: );//打印错误信息return 1;//直接返回}arr tmp;return 0;}新增内存较小时一般是在原有基础上新增空间。两者地址相同。
int* tmp (int*)realloc(arr, sizeof(int) * 100);//新增内存较大时新增内存较大时则会重新开辟一段空间将原来的空间释放。两者地址不同。
2.2 动态内存释放函数
动态内存开辟的空间并不像静态开辟内存的空间会随着一段程序的结束而回收这时就需要我们手动回收否则就会造成内存泄漏。
**内存泄漏Memory Leak**是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。 头文件#include stdlib.h声明void free(void *ptr) ptr – 指针指向一个要释放内存的内存块该内存块之前是通过调用 malloc、calloc 或 realloc 进行分配内存的。如果传递的参数是一个空指针则不会执行任何动作。作用释放之前调用 calloc、malloc 或 realloc 所分配的内存空间。返回值该函数不返回任何值。 下面使用free函数的实例:
int main()
{int* arr (int*)calloc(10, sizeof(int));//开辟十个大小为整型的空间//返回类型强转为int*if (arr NULL)//如果开辟失败{perror(calloc fail: );//打印错误信息return 1;//直接返回}//继续新增空间int* tmp (int*)realloc(arr, sizeof(int) * 100);if (tmp NULL)//如果开辟失败{perror(calloc fail: );//打印错误信息return 1;//直接返回}arr tmp;free(arr);//释放arr所指向的内存arr NULL;return 0;}释放完之后记得将arr置为NULL否则arr指向一段已经回收的空间会变成野指针。
2.3 常见内存分布
⼀般我们在学习C/C语⾔的时候我们会关注内存中的三个区域栈区、 堆区、静态区。 局部变量与函数参数是放在内存的栈区 全局变量static修饰的变量是放在内存的静态区。 堆区是⽤来动态内存管理的。
具体分布如下图 3. 动态内存的常见错误
动态内存开辟就像指针一样一不小心就会酿成大错以下介绍了一些常见的内存开辟错误
3.1 对NULL指针的解引用
void test()
{int* p (int*)malloc(INT_MAX / 4);*p 20; //如果p的值是NULL就会有问题free(p);
}INT_MAX是一个宏定义他表示整型的最大值值为2147483647。当malloc申请的空间太大时存在失败的情况失败返回NULL指针。而系统无法访问NULL指针指向的地址这时编译器会报一个警告: 改正方法
void test()
{int* p (int*)malloc(INT_MAX / 4);if (NULL p){perror(malloc fail: );//打印错误信息return 1;}*p 20;free(p);p NULL;
}这时就体现判断是否为空指针的重要性了
3.2 对动态开辟空间的越界访问
void test()
{int i 0;int* p (int*)malloc(10 * sizeof(int));if (NULL p){perror(malloc fail: );//打印错误信息return 1;//直接返回}for (i 0; i 10; i){*(p i) i; //当i是10的时候越界访问}free(p);pNULL;
}malloc只申请了十个整型大小的空间。for循环循环了十一次越界访问错误信息如下 改正方法
void test()
{int i 0;int* p (int*)malloc(10 * sizeof(int));if (NULL p){perror(malloc fail: );//打印错误信息return 1;//直接返回}for (i 0; i 10; i){*(p i) i; //当i是10的时候越界访问}free(p);p NULL;
}3.3 对非动态开辟内存使用free释放
void test()
{int a 10;int* p a;free(p);pNULL;//ok?
}free()只能释放有动态内存开辟在堆上的空间。p指向的空间是静态内存开辟的无法释放释放就会出错 改正方法
void test()
{int a 10;int* p a;
}静态内存开辟的空间并不需要释放。
3.4 使⽤free释放⼀块动态开辟内存的⼀部分
void test()
{int* p (int*)malloc(100);p;free(p); //p不再指向动态内存的起始位置p NULL;
}p跳过一个整型大小的空间。free()释放p只会释放当前位置开始之后的空间有一个整型大小的空间未被释放造成内存泄漏。 改正方法
void test()
{int* p (int*)malloc(100);free(p); p NULL;
}不能随意改变p指向的位置开辟多少内存就释放多少内存。
3.5 对同⼀块动态内存多次释放
void test()
{int* p (int*)malloc(100);free(p);free(p); //重复释放
}p已经被释放归还给操作系统但是此时p还指向该内存是一个野指针。再次释放p就会出现内存出错问题。 改正方法
void test()
{int* p (int*)malloc(100);free(p);p NULL;
}释放内存之后记得将其置为空指针这样再次free空指针就不会进行任何操作。
3.6 动态开辟内存忘记释放内存泄漏
void test()
{int* p (int*)malloc(100);if (NULL ! p){*p 20;}//内存泄漏
}int main()
{test();
}当我们动态内存申请空间之后必须手动将其释放不会就会出现内存泄漏的问题。
改正方法
void test()
{int* p (int*)malloc(100);if (NULL ! p){*p 20;}free(p);p NULL;
}每次使用完动态内存开辟空间之后记得释放内存。
4. 相关笔试题
4.1 题目一
void GetMemory(char* p)
{p (char*)malloc(100);
}
void Test(void)
{char* str NULL;GetMemory(str);strcpy(str, hello world);printf(str);
}//请问运⾏Test函数会有什么样的结果这段程序有两个经典错误
**内存非法访问:**我们知道传值调用时形参只是实参的临时拷贝对形参的改变无法影响实参这时str仍是空指针而strcpy拷贝会对空指针进行解引用操作对NULL指针解引用会出错 **内存泄漏:**在GetMemory()函数内部动态申请了100字节的空间因为p随着函数结束而被销毁所以已经再也找不到该空间会造成内存泄漏。
改正方法
我们要想改变str就需要传址调用而str本身就是个指针变量传指针变量的地址需要二级指针来接收。使用完之后必须释放内存。
void GetMemory(char** p)
{*p (char*)malloc(100);
}void Test(void)
{char* str NULL;GetMemory(str);strcpy(str, hello world);printf(str);// 释放free(str);str NULL;
}4.2 题目二
char* GetMemory(void)
{char p[] hello world;return p;
}void Test(void)
{char* str NULL;str GetMemory();printf(str);
}这段程序是经典的野指针问题局部变量出了作用就会销毁归还给操作系统而str还能指向这块空间就会形成野指针。 改正方法
因为只有存放在栈区的值才会被销毁所以我们将其放在其他区域如静态区而放在静态区有两种方法static修饰与常量字符串。 const char* GetMemory1(void)
{const char* p hello world;return p;
}char* GetMemory2(void){static char p[] hello world;return p;}void Test(void)
{char* str NULL;str GetMemory1();printf(str);printf(\n);str GetMemory2();printf(str);
}int main()
{Test();return 0;
}输出结果 4.3 题目三
void GetMemory(char** p, int num)
{*p (char*)malloc(num);
}
void Test(void)
{char* str NULL;GetMemory(str, 100);strcpy(str, hello);printf(str);
}
//请问运⾏Test函数会有什么样的结果这又是一个经典的内存泄漏问题——p开辟出内存未被释放。
改正方法
void Test(void)
{char* str NULL;GetMemory(str, 100);strcpy(str, hello);printf(str);free(str);str NULL;
}4.4 题目四
void Test(void)
{char* str (char*)malloc(100);strcpy(str, hello);free(str);if (str ! NULL){strcpy(str, world);printf(str);}
}
//请问运⾏Test函数会有什么样的结果这也是个经典野指针问题str所开辟的空间已经归还给了操作系统这时再将world拷贝进str就会出错。
改正方法
归还内存之后随手将其值为NULL指针后续语句就不会进行。
void Test(void)
{char* str (char*)malloc(100);strcpy(str, hello);free(str);str NULL;if (str ! NULL){strcpy(str, world);printf(str);}
}5. 柔性数组
5.1 柔性数组是什么
C99中结构体中的最后一个元素允许是未知大小的数组这就叫作柔性数组例如
typedef struct st_type
{int i;int a[0]; //柔性数组成员
}type_a;有些编译器会报错⽆法编译可以改成
typedef struct st_type
{int i;int a[]; //柔性数组成员
}type_a;结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。包含柔性数组成员的结构⽤malloc()函数进⾏内存的动态分配并且分配的内存应该⼤于结构的⼤⼩以适应柔性数组的预期⼤⼩。
5.2 柔性数组的大小
依靠我们结构体学过得内存对齐的原则我们可以计算结构体的大小。
typedef struct st_type
{int i;int a[0]; //柔性数组成员
}type_a;
int main()
{printf(%d\n, sizeof(type_a)); return 0;
}输出结果 从上述可知柔性数组成员是不计入结构体大小的。
5.3 柔性数组的使用
柔性数组的使用与结构体使用十分类似具体使用如下
#include stdio.h
#include stdlib.h
typedef struct st_type
{int i;int a[]; //柔性数组成员
}type_a;
int main()
{int i 0;type_a* p (type_a*)malloc(sizeof(type_a) 100 * sizeof(int));//包含柔性数组成员的结构⽤**malloc()函数**进⾏内存的动态分配// 并且分配的内存应该⼤于结构的⼤⼩以适应柔性数组的预期⼤⼩。p-i 100;for (i 0; i 100; i)//存放数据{p-a[i] i;}free(p);return 0;
}5.4 模拟实现柔性数组 先开辟一个结构体大小在开辟一个数组的大小。 柔性数组成员的空间都是malloc开辟的所以模拟的柔性数组也需要malloc开辟。 具体实施如下
#include stdio.h
#include stdlib.h
typedef struct st_type
{int i;int* p_a;
}type_a;
int main()
{//先开辟一个结构体大小type_a* p (type_a*)malloc(sizeof(type_a));p-i 100;//在开辟一个数组大小p-p_a (int*)malloc(p-i * sizeof(int));for (int i 0; i 100; i){p-p_a[i] i;}//释放空间free(p-p_a);p-p_a NULL;free(p);p NULL;return 0;
}5.5 柔性数组的优势
通过与模拟的柔性数组对比我们可以看出柔性数组的优势 便内存释放如果我们的代码是在一个给别人用的函数中你在里面做了二次内存分配并把整个结构体返回给用户。用户调用free可以释放结构体但是用户并不知道这个结构体内的成员也需要free容易造成内存泄漏。所以如果我们把结构体的内存以及其成员要的内存一次性分配好了并返回给用户一个结构体指针用户做一次free就可以把所有的内存也给释放掉 这样有利于访问速度 连续的内存有益于提⾼访问速度也有益于减少内存碎⽚。