数组一旦创建好,就无法更改了,那么我们怎么样能做到将数组大小灵活调整呢?
(^-^):用变长数组!!
变长数组:数组的大小可以用变量指定,但数组创建好后,依然不能调整大小,申请的空间不能灵活调整。
想要申请的空间灵活调整,就要学习动态内存开辟的相关知识
进行动态内存分配的原因:
1.原来内存空间开辟大小是固定的
2.数组在声明的时候,必须指定数组的长度,数组空间一旦确定了大小不能调整,但对于空间的需求,不仅仅是上述的情况,有时我们需要的空间大小在程序运行时才能知道,此时数组的编译时开辟的空间就不能满足需求了
一、malloc
malloc是动态内存开辟函数,用来申请内存
void* calloc (size_t num, size_t size);
该库函数包含在头文件:stdlib.h下
作用
calloc向内存申请一块连续可用的空间,并返回指向这块空间的指针
1.若开辟成功,则返回一个指向开辟好空间的指针
2.若开辟失败,则返回NULL指针,因此calloc的返回值一定要检查
3.返回值类型为void*,所以malloc函数不知道开辟空间的类型,具体在使用的时候由自己来决定
4.如果参数size为0,malloc的行为是标准or未定义的,取决于编译器
用例:
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
return 0;
}
回收
malloc申请的空间怎么回收呢?
1.free回收
2.自己不释放时,程序结束后也会由操作系统进行回收
3.malloc是在堆区申请内存
二、free
void free (void* ptr);
free能释放动态申请的空间
1.如果参数ptr指向的空间不是动态内存开辟的,那么free函数的行为是未定义的
2.如果参数ptr是NULL指针,则函数什么事都不做
例:
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)malloc(10*sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*(p + i) = i;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p);
p = NULL; //记得置空,否则野指针
return 0;
}
三、calloc
void* calloc (size_t num, size_t size);
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
free(p);
p = NULL; //记得置空,否则野指针
return 0;
}
打印结果:
若改为malloc则结果为随机值(malloc申请的空间不会被初始化,calloc申请的空间会被初始化)
四、realloc
realloc函数的出现让动态内存管理更加灵活,适用于对内存大小灵活调整
void* realloc (void* ptr, size_t size);
ptr:要调整的内存地址
size:调整后内存大小
返回值:调整后的内存起始位置
这个函数在调整原空间大小的基础上,还会将原来的内存数据移动到新空间。
realloc调整时有两种情况
1.原有空间后有足够大的空间-->追加空间(返回原地址)
2.原有空间后无足够大的空间-->找到另外一块足够大的空间,原数据拷贝到新空间,原空间释放(返回新地址)
使用:
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
printf("%d ", p[i]);
}
int* ptr = (int*)realloc(p, 40); //不要拿p--如果没扩容成功,以前的也不能用了
if (*ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
free(p);
p = NULL; //记得置空,否则野指针
return 0;
}
realloc也可以缩小空间
当realloc的第一个参数为空指针时,功能相当于malloc
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* ptr = (int*)realloc(NULL, 40);
return 0;
}
五、常见错误
对空指针的解引用
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(20);
for (int i = 0; i < 5; i++)
{
*(p + i) = i; //p可能为空指针
}
return 0;
}
正确写法:
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
return 1;
}
for (int i = 0; i < 5; i++)
{
*(p + i) = i; //p可能为空指针
}
return 0;
}
对动态内存开辟空间的越界访问
#include<stdio.h>
#include<stdlib.h>
int main()
{
int* p = (int*)malloc(20);
if (p == NULL)
{
return 1;
}
for (int i = 0; i < 20; i++)
{
*(p + i) = i; //p可能为空指针
}
return 0;
}
对非动态内存开辟空间使用free释放
#include<stdio.h>
#include<stdlib.h>
void test()
{
int a = 10;
int* p = &a;
free(p);
}
使用free释放一块动态内存开辟的一部分 (内存释放不支持分期付款)
#include<stdio.h>
#include<stdlib.h>
void test()
{
int* p = (int*)malloc(100);
p++;
free(p); //p不再指向动态内存的起始位置
}
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*p = i + 1;
p++;
}
free(p);
p = NULL;
return 0;
}
对同一块内存多次释放
#include<stdlib.h>
#include<stdio.h>
int main()
{
int* p = (int*)calloc(10,sizeof(int));
if (p == NULL)
{
perror("calloc");
return 1;
}
for (int i = 0; i < 10; i++)
{
*p = i + 1;
p++;
}
free(p);
//
//卡皮巴拉~
//
free(p);
p = NULL;
return 0;
}
动态开辟内存的空间忘记释放
可能造成内存泄漏
#include<stdio.h>
#include<stdlib.h>
void test()
{
int* p = (int*)malloc(40);
if (p == NULL)
{
return;
}
}
int main()
{
test();
while (1);
return 0;
}
六、经典笔试题
1.
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str, "hello world!!");
printf(str);
}
int main()
{
Test();
return 0;
}
程序崩哩。。。原因有二:
1.对NULL指针进行解引用操作(形参是实参的一份临时拷贝,对形参的修改不会影响实参,str依旧为空指针),导致程序崩溃
2.malloc申请的空间,没有释放,导致内存泄漏
tips:
这是一种打印方式哦:
#include<stdio.h>
int main()
{
char* p = "hello world!!";
printf(p);
return 0;
}
我们可以这样改:
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
void GetMemory(char** p)
{
*p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(&str);
strcpy(str, "hello world!!");
printf(str);
}
int main()
{
Test();
return 0;
}
还可以这样:
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
char* GetMemory(p)
{
p = (char*)malloc(100);
return p;
}
void Test(void)
{
char* str = NULL;
str=GetMemory();
strcpy(str, "hello world!!");
printf(str);
free(str);
str = NULL;
}
int main()
{
Test();
return 0;
}
2.
#include<string.h>
#include<stdlib.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;
}
那这样能不能正常打印hello world呢?
还是不可以,结果乱码
原因:返回栈空间地址,数组是局部数组,消栈了
3.
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
printf("hehe\n");
printf("%d", *p);
return 0;
}
猜猜输出结果是什么?
同样的代码,将printf("hehe");这行注释掉,就可以打印10了
原因:printf可能会覆盖之前的栈空间
4.
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
void GetMemory(char**p,int num)
{
*p = (char*)malloc(num);
return p;
}
void Test(void)
{
char* str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello world!!");
printf(str);
}
int main()
{
Test();
return 0;
}
这段代码能正常打印hello world吗?
当然可以,但是最好free,否则容易导致内存泄漏
5.
#include<string.h>
#include<stdlib.h>
#include<stdio.h>
void GetMemory(char**p,int num)
{
*p = (char*)malloc(num);
return p;
}
void Test(void)
{
char* str = (char*)malloc(100);
strcpy(str, "hello ");
printf(str);
free(str);
if (str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
int main()
{
Test();
return 0;
}
这段代码能不能正常打印hello world呢?
可以是可以,但是程序出现了很大的问题
free(str); //str为野指针
strcpy(str, "world"); //非法访问
七、柔性数组
概念
结构中最后一个元素允许是未知大小的数组,叫做柔性数组成员
typedef struct st_type
{
int i;
int a[0]; //柔性数组成员,0表示未知
}type;
有些编译器报错,无法编译,可改为:
typedef struct st_type
{
int i;
int a[];
}type;
特点
1.结构中的柔性数组成员前面必须至少一个其他成员
2.sizeof返回的这种结构大小不包括柔性数组的内存
3.包含柔性数组的成员结构用malloc函数进行内存的动态分配,并且分配的内存应大于结构的大小,以适应柔性数组的预期大小
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
int i;
int a[];
}type;
int main()
{
type* p = (type*)malloc(sizeof(type) + 10 * sizeof(int));
if (p == NULL)
{
perror("malloc");
return 1;
}
int i = 0;
p->i = 100;
for (int i = 0; i < 10; i++)
{
p->a[i] = i;
}
//希望a数组变长为60个字节
type* ptr = (type*)realloc(p, sizeof(type) + 15 * sizeof(int));
if (ptr != NULL)
{
p = ptr;
ptr = NULL;
}
else
{
perror("realloc");
return 1;
}
free(p);
p = NULL;
return 0;
}
玩波花的:
#include<stdio.h>
#include<stdlib.h>
typedef struct st_type
{
int i;
int* a;
}type;
int main()
{
type* p = (type*)malloc(sizeof(type));
if (p == NULL)
{
perror("malloc\n");
return 1;
}
p->i = 100;
p->a = malloc(10 * sizeof(int));
if (p->a == NULL)
{
perror("malloc\n");
return 1;
}
for (int i = 0; i < 10; i++)
{
p->a[i] = i;
}
int* ptr = (int*)realloc(p, 15 * sizeof(int));
if (ptr == NULL)
{
perror("malloc");
return 1;
}
else
{
p = ptr;
ptr = NULL;
}
//使用
//
free(p->a);
p->a = NULL;
free(p);
p = NULL;
return 0;
}
那么这两种方案到底哪种好呢?
方案一好!!!
好处一:方便内存释放
如果我们的代码写在一个给别人用的函数中,你在里面进行了两次内存分配,并将整个结构体返回给用户,用户使用free释放结构体,但他不知道内部成员也要free这件事,所以如果我们把结构体的内存及成员需要的内存一次性分配好,并返回给用户一个结构体指针,让他一次就可以全部free掉
好处二:利于访问速度
连续的内存有益于提高访问速度,也有益于减少内存碎片,柔性数组在内存中连续存储,对连续存储的访问效率高于不连续的