一、常用函数及易错点
1.malloc()函数
- 如果开辟成功,则返回一个指向开辟好的空间的指针
- 如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查
- 返回值的类型使void*,所以malloc函数并不知道开辟空间类型,具体使用的时候要自己做强转
- 如果参数size为0,malloc的行为是标准是未定义的,取决于编译器
- 使用时,一定要判断当前位置是否为NULL
2.free()函数
- 用来释放动态开辟的内存,不包括数组
- 如果参数ptr指向的空间不是动态开辟的,那free函数的行为是未定义的
- 如果参数ptr是NULL指针,则函数什么事都不做
3.calloc()函数
- 自动初始化为0后,返回地址
4.realloc()函数
- 调整动态开辟内存的空间大小
- 当malloc开辟空间不够实用时,使用reallo()追加空间
- 得用一个新的变量来接收realloc函数得返回值【避免原地址丢失】
- 运作原理:当添加空间过大时(可能把一些内存覆盖掉),realloc会重新开辟一块新的空间,将原来数据存放到新空间,原空间free,返回新空间地址;相反,当添加空间较小时,在原有的地址尾部追加相应空间,返回原地址
5.综合应用
#include <stdio.h> #include <stdlib.h> #include <string.h> //void* malloc(size_t size) //void* free(void* memblock) //void*(size_t num,size_t size) //void*realloc(void* memblock,size_t size); //size 代表空间大小 int main() { //开辟空间不初始化,相较calloc,效率高 int* p1 = (int*)malloc(10* sizeof(int)); //开辟空间初始化为 0 ,相较malloc,效率较低 int* p2 = (int*)calloc(10, sizeof(int)); //用realloc开辟空间 int* p3 = realloc(NULL,40);//与malloc(40)等价 //增加健壮性【常见错误1】 //一定要检查空间。不够,报告提示 if(p1 == NULL) { printf("%s\n",strerror(errno)); } else { //内存够用 // 应用 :正常使用空间,存入0-9 int i = 0; for(i = 0;i < 10;i++) { *(p1 + i) = i; } for (i = 0;i < 10;i++) { printf("%d ",*(p1 + i)); } } //追加空间realloc int* ptr = realloc(p1,4000); //当开辟失败时,可能会使原地址丢失 if(ptr != NULL) { p1 = ptr; } //当动态申请的空间不再使用的时候,释放空间,将空间还给操作系统 free(p1); //但是,我们这个 p 仍然指向那一块地址,增加健壮性,将p=NULL p1 = NULL; return 0; }
6.常见内存应用经典错误
1.对NULL指针的接应用操作
void test1()
{
int* p = (int*)malloc(INT_MAX/4);
*p = 20;//如果malloc失败,p值是NULL【非法地址】,这样写法是错误
free(p);
}
//解决方法:
void test1()
{
int* p = (int*)malloc(INT_MAX/4);
if(p == NULL)
{
//判断是否NULL,是则抛出错误
printf("%s\n",strerror(errno));
}
*p = 20;//如果malloc失败,p值是NULL【非法地址】,这样写法是错误
free(p);
}
2.对动态开辟的内存的越界访问
int test2()
{
int*p = (int*)malloc(5 * sizeof(int));
if(p == NULL)
{
return 0;
}
else
{
int i = 0;
//开辟了5个空间,而访问了10空间,访问不合法
for (i = 0;i <10;i++)
{
*(p + i) = i;
}
}
free(p);
p = NULL;
}
3.对非动态内存【数组等】使用free函数
void test3()
{
int a = 10;
int *p = &a;
free(p);
}
4.使用free释放动态开辟内存的一部分
int test4()
{
int*p = (int*)malloc(40);
if(p == NULL)
{
return -1;
}
int i = 0;
for(i = 0;i < 10;i++)
{
*p++ = i;
}
free(p);//err,此时p指向最后一个元素,而你想要是释放p前边的元素,结果把后边释放系统会报错!
return 0;
}
5.对同一块内存多次释放
int test5()
{
int* p = (int*)malloc(40);
if(p == NULL)
{
return -1;
}
free(p);
//...
free(p);
}
//解决方法;
int test5()
{
int* p = (int*)malloc(40);
if(p == NULL)
{
return -1;
}
free(p);
p = NULL;//可以避免,但是要养成 释放 开辟 两者相组合
//...
free(p);
}
6.动态开辟内存忘记释放【内存泄露】
void test6()
{
while(1)
{
malloc(1);
}
}
7.查找代码错误
问题【1】
#include <string.h>
#include <malloc.h>
#include <stdio.h>void Getmemory(char* p)
{
p = (char*)malloc(100);
}
void Test()
{
char* str = NULL;
Getmemory(str);//err,这里传入得是形参,不是地址
strcpy(str,"hello");
printf(str);
}
int main()
{
Test();
return 0;
}
//解决方法
//【修改后】
void Getmemory(char** p)
{
*p = (char*)malloc(100);
}
void Test()
{
char* str = NULL;
Getmemory(&str);//修改后
strcpy(str,"hello");
printf(str);//printf("%s",str);
free(str);
str = NULL;
}
问题【2】
//返回栈空间的地址的问题,非法访问
#include <string.h>
#include <malloc.h>
#include <stdio.h>char* Getmemory()
{
char p[] = "hello";
return p;
}
void Test()
{
char* str = NULL;
str = Getmemory();//err,调用完Getmemory后,这个局部变量开辟的地址,会释放掉,这个str接收的就不知道是谁的地址,打印出随机值
printf(str);
}
int main()
{
Test();
return 0;
}
//解决方法
//【修改后】
char* Getmemory()
{
static char p[] = "hello";//静态区,而char p[] = "hello";这是栈区,会销毁掉
return p;
}
void Test()
{
char* str = NULL;
str = Getmemory();//err,调用完Getmemory后,这个局部变量开辟的地址,会释放掉,这个str接收的就不知道是谁的地址,打印出随机值
printf(str);
free(str);
str = NULL;
}
问题【3】
#include <malloc.h>
#include <string.h>
#include <stdio.h>//篡改动态内存区的内容
void Test()
{
char* str = (char*)malloc(100);
strcpy(str,"hello");
//释放str后,但是没有置为NULL,此时str为野指针,if(str != NULL)语句不起作用
free(str);
if(str != NULL)
{
strcpy(str,"world");
}
printf(str);
}
int main()
{
Test();
return 0;
}
二、柔性数组
1.特点
- 在结构体中数组大小是可以调整的
- 结构体中arr数组是不占空间的
- 方便内存释放,提高访问速度,减少内存碎片
- 指针也可以实现类似柔性数组作用
2.应用
【1】用柔性数组开辟
#include <stdio.h> #include <malloc.h> struct S { int n ; int arr[];//未知数组,数组大小是可以调整的 }; int main() { struct S s; printf("%d",sizeof (s));//输出为 4 ,其中,结构体中arr数组是不占空间的 // sizeof(struct S)是这个结构体的大小,但是,因为它含有柔性数组,我在后边5* sizeof(int)相当于给数组的大小 struct S *ps = (struct S*)malloc(sizeof(struct S) + 5* sizeof(int)); //随便给n赋值 ps->n = 100; int i = 0; //给柔性数组赋值 for (i = 0; i < 5;i++) { printf("%d ",ps->arr[i]=i); } //添加空间 struct S*ptr = realloc(ps,44); //增加健壮性,避免新开辟空间指向为NULL或者开辟失败 if(ptr != NULL) { ps = ptr; } //开辟成功,做拼接 for (i = 5;i < 10;i++) { printf("%d ",ps->arr[i]=i); } //增加健壮性,释放内存,“有借有还,再借不难 o.0” free(ps); ps = NULL; return 0; }
【2】指针实现类似柔性数组的开辟
#include <malloc.h> #include <stdio.h> //第二种应用 //用指针的方法 struct S { int n; int *arr; }; int main() { //定义结构动态开辟 // 1.用malloc开辟结构S的大小 struct S*ps = (struct S*)malloc(sizeof(struct S)); // 2.为了让空间变大,让arr指向一块空间,不建议连续使用malloc【容易使内存碎片化】 ps->arr = malloc(5* sizeof(int)); printf("%d\n", sizeof(struct S));//此时结构体大小为16字节 int i = 0; //给数组arr赋值 for (i = 0;i < 5;i++) { printf("%d ",ps->arr[i] = i); } //调整大小 // 1.开辟空间 int* ptr = (int*) realloc(ps->arr,5* sizeof(int)); // 2.判断ptr是否合法,即内存是否够用,或者是否开辟成功 if(ptr != NULL) { // 3.指向ps中的数组 ps->arr = ptr; } //对开辟的空间进行赋值 for (i = 5;i < 10;i++) { printf("%d ",ps->arr[i]=i); } //释放空间 free(ps); ps->arr = NULL; ps = NULL; return 0; }