我们以往定义数组,都是这么定义的:
int nums[10] = {0};
以这种方式开辟空间有两个特点:
- 空间开辟的大小是固定的
- 数组在声明的时候,必须指定数组的长度,它所需要的内存在编译时分配
因此就导致了这样一个现象:我们无法在后续的过程中修改数组的大小,这是一个十分麻烦的事情
而为了解决这个问题,我们就需要学习动态内存开辟了
注:需要头文件
需要知道,和静态开辟空间不一样,计算机是在堆上开辟的动态空间
void* malloc (size_t size);
这个函数向内存申请一块大小为size
字节的连续可用的空间,并返回指向这块空间的指针
malloc
的返回值作有效性的判断void *
,因此当我们用指针变量接受这个返回值时,我们要将这个返回值强制转换为需要的类型size
为0,malloc
的行为是标准未定义的,例如:
#include
#include
int main()
{
//向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
int *nums = (int*)malloc(sizeof(int) * 10);
//检验返回值的有效性
if (nums == NULL)
{
perror("malloc");
return 1;
}
//循环打印nums指向空间的值
for (int i = 0; i < 10; i++)
printf("%d\n", *(nums + i));
free(nums);
nums = NULL;
return 0;
}
output:
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
-842150451
需要注意:凡是动态申请的内存,除非整个程序结束,申请的内存是不会主动归还给系统的,为了避免内存泄漏,我们应该使用函数free
来将申请的内存释放
void free (void* ptr);
free函数用来释放动态开辟的内存
指向的空间不是动态开辟的,那
free`函数的行为是未定义的ptr
是NULL指针,则函数什么事都不做例如:
#include
#include
int main()
{
//向内存申请40个字节的空间,并将返回的指针强制转换成int*型,并将其赋予指针nums
int *nums = (int*)malloc(sizeof(int) * 10);
//检验返回值的有效性
if (nums == NULL)
{
perror("malloc");
return 1;
}
free(nums); //释放ptr所指向的动态内存
nums = NULL; //将野指针置空
return 0;
}
void* calloc (size_t num, size_t size);
num
个大小为size
的元素开辟一块空间,并且把空间的每个字节初始化为0malloc
的区别只在于calloc
会在返回指针之前把申请的空间的每个字节初始化为0例如:
##include<stdio.h>
#include
int main()
{
//申请10个大小为4的空间,并让指针nums指向它
int* nums = (int*)calloc(10, sizeof(int));
//判断返回值的有效性
if(nums == NULL)
{
perror("calloc");
return 1;
}
//打印nums指向空间的元素
for (int i = 0; i < 10; i++)
printf("%d\n", *(nums + i));
free(nums);
nums = NULL;
return 0;
}
output:
0
0
0
0
0
0
0
0
0
0
所谓的动态内存管理,“内存管理”我们好像已经会了,那这个“动”又是怎么做到的呢?我们前面所学的malloc, calloc
好像并不能让申请的内存动起来呀。
要想实现对内存的增加或减小,就需要我们的函数realloc
:
void* realloc (void* ptr, size_t size);
ptr
是要调整的内存地址
malloc
的功能相似,会直接返回一个指向大小为size
字节空间的指针size
为调整之后的大小
返回值为调整之后的内存的起始位置
扩容后的空间不会被初始化
这个函数调整源内存空间大小的基础上,还会将原来内存中的数据移动到新的空间
ealloc
在调整内存空间时存在两种情况:
如果调整失败,就会返回空指针,为了考虑到这种情况,我们应该避免以下的代码:
//error example
#include
#include
int main()
{
int* nums = (int*)malloc(10 * sizeof(int));
if (nums == NULL)
{
perror("malloc");
return 1;
}
//如果开辟失败,那么nums就成了空指针,前面nums管理的40个字节的空间就找不到了,这样就造成了内存泄漏
nums = (int*)realloc(nums, 20 * sizeof(int));
if (nums == NULL)
{
perror("realloc");
return 1;
}
free(nums);
nums = NULL;
return 0;
}
//right example
#include
#include
int main()
{
int* nums = (int*)malloc(10 * sizeof(int));
if (nums == NULL)
{
perror("malloc");
return 1;
}
//先用一个中间变量temp接受
int* temp = (int*)realloc(nums, 20 * sizeof(int));
if (temp == NULL)
{
perror("realloc");
return 1;
}
//当temp有效时,再用nums接受
nums = temp;
free(nums);
nums = NULL;
return 0;
}
最后,再对realloc
的具体使用举个例子:
#include
#include
int main()
{
//先动态开辟40个字节的内存
int* nums = (int*)malloc(10 * sizeof(int));
if (nums == NULL)
{
perror("malloc");
return 1;
}
//将这块空间初始化为0
memset(nums, 0, 10 * sizeof(int));
//将这块空间扩容到60个字节,并先用中间变量temp接受
int* temp = (int*)realloc(nums, 15 * sizeof(int));
if (temp == NULL)
{
perror("realloc");
return 1;
}
//确定temp有效后再用nums指向temp
nums = temp;
//打印扩容后空间的数据
for (int i = 0; i < 15; i++)
{
printf("%d\n", nums[i]);
}
//释放内存
free(nums);
nums = NULL;
return 0;
}
output:
0
0
0
0
0
0
0
0
0
0
-842150451
-842150451
-842150451
-842150451
-842150451
void test()
{
int *p = (int *)malloc(sizeof(int));
*p = 20; //如果p为空指针,就会有问题,一定先要检查返回指针的有效性
free(p)
}
void test()
{
int *p = (int *)malloc(10 * sizeof(int));
if(NULL == p)
{
perror("malloc");
return 1;
}
for(int i = 0; i <= 10; i++)
*(p + i) = i; //当i是10的时候就会越界访问
free(p);
}
void test()
{
int nums[10] = {0};
free(nums);
}
void test()
{
int *p = (int *)malloc(10 * sizeof(int));
if(NULL == p)
{
perror("malloc");
return 1;
}
free(p);
free(p);
}
void test()
{
int *p = (int *)malloc(10 * sizeof(int));
if(NULL == p)
{
perror("malloc");
return 1;
}
for(int i = 0; i < 10; i++)
*(p + i) = i;
}
C99中,结构体中的最后一个元素允许是位置大小的数组,这就叫做柔性数组
例如:
typedef struct ST
{
int i;
int a[0]; //柔性数组成员,也可以写成 a[];
}ST;
柔性数组前至少有一个其他成员
sizeof
返回的结构体大小不包含结构中柔性数组的内存,例如对于上面的代码:
printf("%d\n",sizeof(ST));
output:
4
包含柔性数组成员的结构用malloc函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小,同样,也可以用realloc
进行增容,例如:
#include
#include
typedef struct ST
{
int i;
int a[]; //柔性数组成员
}ST;
int main()
{
ST *st1 = (ST*)malloc(sizeof(ST) + sizeof(int) * 10); //向内存申请大小为结构大小加10个整型的内存空间
if (NULL == st1)
{
perror("malloc");
return 1;
}
st1->i = 10;
//给空间赋值
for (int i = 0; i < 10; i++)
(st1->a)[i] = i + 1;
//打印空间元素
for (int i = 0; i < 10; i++)
printf("%d\n", (st1->a)[i]);
//增容,将内存扩大5个int型
ST* temp = (ST*)realloc(st1, sizeof(ST) + sizeof(int) * 15);
if (NULL == temp)
{
perror("realloc");
return 1;
}
st1 = temp;
//打印空间数据
for (int i = 0; i < 15; i++)
printf("%d\n", (st1->a)[i]);
//释放动态内存
free(st1);
st1 = NULL;
return 0;
}
由上面的分析我们可以看到,我们完全可以在结构体里面创建一个整形指针(其他类型也可以),然后对其进行动态内存开辟就可以完全替代柔性数组的功能,因此柔性数组这一功能并不常用,我们仅作了解即可