动态内存开辟和扩容
- 动态内存介绍
- malloc
- calloc
- realloc
- 动态内存常见错误
- 动态内存面试题
- 柔性数组
动态内存介绍
在C语言中,要想开辟一块空间,比如说数组,变量,但是不论是数组还是变量,在制定数据类型并定义好后,都不能改变大小。
在定义好变量和数组之后,变量和数组的大小是不能被改变的。
假如我们开辟了40个字节的数字,当数组被数据放满后再想添加数据怎么办呢?在C语言中有一个概念叫做动态内存,我们知道数组名代表的就是数组的地址,数组就是在内存中开辟一块连续的空间,而动态内存就是在内存中开辟一块空间,然后把这块空间的地址返回给一个指针接收,当这块动态内存不够用时,我们可以对它进行扩容,可以理解成可以变长的数组。
malloc
malloc的定义:
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
开辟好内存的首地址 malloc(要开辟的字节个数)
如果开辟成功返回内存的首地址,开辟失败返回NULL
这里返回的是void*的指针,接收地址需要进行强制类型转换
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
int main()
{
//malloc
//开辟一个20个字节的空间
int* p = (int*)malloc(5 * sizeof(int));//这里需要将malloc的返回值强制转换成int类型
//使用说明类型的变量接收返回值,就强制转换成什么类型
//判断是否位空指针
if(p == NULL)
{
perror(""malloc);
return 1;
}
//在这块空间中存入数据
int i = 0;
for (i = 0; i < 5; i++)
{
p[i] = i;
}
//打印输出
for (i = 0; i < 5; i++)
{
printf("%d ",p[i]);
}
//释放动态内存
free(p);
p = NULL;
return 0;
}
free:
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做。
动态内存和数组的释放:
数组在程序结束后,会自动销毁,然后把内存返回给操作系统,而使用动态内存开辟的空间是需要free函数来手动释放的,在释放完动态内存后,而指针p却还存着之前开辟空间的地址,而之前的空间已经被释放,这个时候p就成为了野指针,所以需要将p置为空。
而不论是malloc,calloc还是realloc,都需要和free配对,也就是说只要使用了动态内存,就要使用free,不然就会造成内存泄漏。
错误示范:
int arr[10];
arr = (int*)malloc(20);
这样的代码是贬义不过去的,虽然arr代表的数组的地址,而malloc的返回值也是一个地址,但是,arr在创建好之后,地址是不能被改变的,而由malloc开辟的动态内存地址在后期是可以改变的。
calloc
calloc的定义:
开辟好内存的首地址 calloc(要开辟多少个元素,元素的大小)
函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0
与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0
malloc和calloc在使用上是很相似的,区别就是malloc只能制定开辟多少个字节,calloc可以制定开辟多少个元素,并且初始化成0.
realloc
realloc的定义:
开辟好内存的首地址 calloc(地址,扩容后的大小)
ptr 是要调整的内存地址
size 调整之后新大小
返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到 新 的空间。
realloc的作用:
在使用malloc或者calloc后,想要扩容内存,这个时候就要用到realloc
我们来调试看一下是否像上图说的那样开辟成功:
首先看到开辟好以后,里面是随机值:
开辟好的地址:
然后赋值:
扩容空间,后5位变成随机值:
再次赋值:
扩容完成后的地址:
扩容后返回的地址分两种情况:
情况1:原有空间之后有足够大的空间
情况2:原有空间之后没有足够大的空间
我们俩画图演示一下:
情况1:
开辟好之后返回内存1原有的地址情况2:
注意:内存和内存中间是有额外空间的,并不是挨着的
我们在vs上示范一下:
使用malloc开辟好的空间地址是:80 3a fa 64
在使用realloc扩容10000个字节后,原有空间后面的内存看到是不够的,这个时候会找一块新的大小合适空间拷贝过去,然后返回这个块空间的地址:
此时返回的地址变成了:10 e8 fa 64
动态内存常见错误
1,对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
2,对动态开辟空间的越界访问
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
}
3,对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
4,使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
5,对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
6,动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
动态内存面试题
1,请问运行Test 函数会有什么样的结果?
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
首先我们创建了一个 char类型的指针,赋为NULL,然后调用GetMemory函数,函数内部用一个指针接收,然后开辟一块100字节的动态内存,将地址存入p,这个时候在使用strcpy拷贝字符串到str中,注意这里的p是一个单独的指针,str传过去只是把NULL赋给了p,所以str里面还是NULL,而且在函数内部开辟的动态内存并没有被释放。
2,请问运行Test 函数会有什么样的结果?
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
在函数内部创建了一个数组,并且赋值,然后返回数组的地址,注意:函数在使用结束后,里面所创建的变量和数组都会被销毁,这个时候把p的地址传给str,str就成为了野指针。
3,请问运行Test 函数会有什么样的结果?
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
这里将str的地址传给一个二级指针来接收,二级指针解引用,就是str,将开辟好的100个字节的动态内存地址返回给str,然后再拷贝字符串。
但是没有释放动态内存。
4,请问运行Test 函数会有什么样的结果?
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
这里将开辟的动态内释放后,但是str依旧存着之前的地址,这个时候str就成了野指针,再对它进行赋值,就会造成越界访问
柔性数组
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
struct S
{
int a;
int b[];
};
在结构体中,最后一个数组可以不设置长度,或者将大小设置位0,这就叫做柔性数组。柔性数组不能单独放在结构体中,前面必须有其他变量或者数组。
柔性数组的特点:
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用malloc 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
在使用sizeof对带有柔性数组的结构体计算大小的时候,柔性数组是不参与的,只会计算柔性数组之前的变量大小。
柔性数组的使用:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct S
{
int a;
int b[];//柔性数组
};
int main()
{
//使用一个struct s的指针来接收
//先计算出柔性数组之前变量的大小
//再加上柔性数组的大小
struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
return 0;
}
柔性数组扩容:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct S
{
int a;
int b[];//柔性数组
};
int main()
{
//使用一个struct s的指针来接收
//先计算出柔性数组之前变量的大小
//再加上柔性数组的大小
struct S* p = (struct S*)malloc(sizeof(struct S) + sizeof(int) * 10);
//使用realloc对柔性数组进行扩容
//还是先计算出柔性数组之前变量的大小
//再对柔性数组进行扩容
p = (struct S*)realloc(p, sizeof(struct S) + sizeof(int) * 20);
return 0;
}
柔性数组的其他用法:
既然柔性数组是可以扩容的,那我们可以将柔性数组改成一个指针,再用这个指针进行malloc开辟动态内存,在使用realloc扩容:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
struct S
{
int a;
int* b;//柔性数组
};
int main()
{
//先开辟好柔性数组之前的变量空间
struct S* p = (struct S*)malloc(sizeof(struct S));
//再对柔性数组进行开辟
p = (struct S*)malloc(sizeof(int) * 10);
//之间对柔性数组扩容,不用管之前的变量空间
p->b = (int*)realloc(p, sizeof(int) * 20);
return 0;
}
以上就是动态内存的详解,如有错误,欢迎指正