欢迎来到博主的专栏——c语言进阶指南
博主id已更新:
文章目录
- 动态内存分配
- malloc
- 动态内存的释放
- free
- 其他的动态内存管理函数
- calloc
- realloc
- 使用realloc函数调整动态内存空间
- 使用realloc函数分配动态内存空间
动态内存分配
动态内存分配是内存分配的一种方法,与此相对的是静态内存分配,在前面我们已经知道了在创建如下变量时,系统会为变量划分一个内存区域
int i;//四个字节的空间
char c;//一个字节的空间
int arr[10];//四十个字节的空间
int *pi;//4/8字节的空间
这些变量的存储形式有以下特点:
(1)这些变量的大小由声明的类型决定的。
(2)这些变量的生命周期取决于创造这个变量的区域,关键字,而非程序。
(3)这些变量的大小在运行过程中不会改变。
如果C语言只有这种特点的变量的话,能实现的操作会受到限制。
比如在创建一个班级的目录时,A班今年有38人,我创建的数组是38个元素,明年A班来了两个转学生,那么是不是又要将整个程序修改一遍呢?当然可以设计一个100元素的数组,但是这又会造成内存浪费了,总之静态内存的管理虽然简单,但是在实现一些操作上会带来一些影响
为了让一些操作变得更加灵活,使用动态内存可以让程序员对自己的程序更加掌握,动态内存的大小由程序决定,生命周期也由程序决定,在程序运行的过程中还能对内存区域进行操作。我们先来了解一下动态内存分配的函数
malloc
malloc的函数原型如下:
(1)size是一个size_t类型的参数,意思是此次需要开辟的空间,单位字节。
(2)malloc函数会开辟size个字节的空间,这些空间是连续的,随机的,开辟成功后会返回这个空间的起始地址
(3)如果开辟失败,返回空指针
malloc(4);
可以看到这个空间是一个四个字节的空间,这个空间是没有数据类型,或者说这个空间是作为什么类型的数据来使用是由程序员决定的,可以将其当做一个int类型的数据,也可以用作一个char类型,由4个元素组成的数组
malloc函数开辟的空间是随机的,所以这个空间的起始地址也是随机的,一个随机的地址我们是难以直接使用的,应该使用一个指针指向这个空间来方便访问这个空间.
如果想要把这个空间当成int类型的数据来使用,可以用int*类型的指针来指向。
int *p;
p=malloc(4);
*p=0x11223344;
那么这段空间的数据就会变成(小端环境下)
如果想要当成char类型的数组来转换,可以用char*类型的指针来指向
char* p;
p = malloc(4);
*(p) = 'a';
*(p + 1) = 'b';
*(p + 2) = 'c';
*(p + 3) = '\0';
前面提到过这个函数的返回值是一个void类型的指针,所以我们可以在这个过程加上类型转换(不加也没有影响)
int *p=(int*)malloc(4);
开辟的空间用作数组时,要注意p是保持指向起始地址的(这对内存的释放很重要),在指针章节中我们曾通过移动指针来实现对数组进行赋值。
int* p = malloc(sizeof(int) * 10);//在内存中申请一个10个int类型的变量的连续空间
for (int i = 0; i < 10; i++)
{
*p++=i;//由于指针自加,导致指针的指向发生移动,这个指针p不再指向起始地址
}
但是动态内存的指针一定要尽可能的指向起始地址,所以得换个方法来赋值,这里给上常用的两种方法
(1)再创建一个指针,让这个指针来移动实现赋值
(2)不移动指针,让指针通过加减的算术运算指向需要操作的地址(推荐)
这里写出第二种方法下的赋值操作
int main()
{
int* p = malloc(sizeof(int) * 10);//在内存中申请一个10个int类型的变量的连续空间
for (int i = 0; i < 10; i++)
{
*(p + i) = i;//p没有被移动,而是通过算术来定位。
}
}
动态内存的释放
局部变量的作用域是有一定范围的,当程序运行超过这个范围时,内存会自动释放。这个变量也就不能再使用了。
而动态内存的释放则不一样,前面已经提到了动态内存是更加灵活的内存分配形式,内存释放的节点是由程序员决定的。而动态内存的释放需要用到一个free函数
free
free的函数原型如下:
(1) free函数的参数是一个viod*的指针,参数需要是指向一个动态开辟的内存空间的起始地址的指针
(2)free没有返回值
free函数的作用是将这个起始地址的动态内存给释放掉,使用free函数要注意以下几点
(1)free函数的指针一定要指向一个动态开辟好的空间,如果这个指针指向的空间不是动态开辟的内存空间,会造成未定义的结果
未定义的结果:由于c标准中没有定义这个行为会造成什么结果,所以这个代码产生的效果由编译器决定(即不同的编译可能会导致不同的结果)
以vs2022为例
int a = 0;
int* pa = &a;
free(pa);
可以发现在执行这段代码时vs2022会报错
执行一个未定义的行为会导致未知的结果,所以对于每一个未定义的行为都应该不去使用,即使这个行为会造成正向结果,因为这个代码是不具有可移植性的(不同的编译器会有不同的结果)。而且更严重的后果可能是整个项目的崩溃
(2)free函数参数的指针,必须指向一个动态内存开辟的空间的起始地址,如果在程序的过程中指针发生移动,使得这个指针指向的空间是动态开辟的内存空间,但指针不再指向空间的起始地址,这个使用方式也是错误的
int* p = malloc(40);
for (int i = 0; i < 3; i++)
{
*p++ = i;
}
free(p);
可以发现,即使执行程序后的p指向的仍是动态内存空间,但不再是空间的起始地址,那么这个程序就是错误的。
尝试运行可以发现编译器报错
其他的动态内存管理函数
calloc
calloc函数的函数原型如下:
calloc函数的作用与malloc的作用类似,都是通过参数开辟一定的动态内存空间,但是calloc在函数的参数上与malloc不同。
其中num是此次开辟的元素个数
size是一个元素占用的大小,单位字节
也就是说如果想要开辟40字节的空间,那么程序应该这么写
malloc(40);
calloc(4,10);
除此之外calloc肯定会有与malloc不同的作用,不然这个函数就没有存在的意义。
calloc函数开辟的动态空间是初始化好的空间,而malloc函数开辟的空间是未初始化的,在vs2022的内存监视窗口中可以看出
这是malloc函数开辟的空间,其中cd说明这片数据还没有被初始化。
这是calloc开辟的内存空间,可以发现这些数据都被初始化成了0.
并非所有的编译器都有这么直观的窗口,我们也可以使用这段代码来发现这两个函数的区别。
int main()
{
int* ptr1 = malloc(40);
int* ptr2 = calloc(4, 10);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%x ", *(ptr1+i));
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%x ", *(ptr2+i));
}
free(ptr1);
free(ptr2);
return 0;
}
从代码的运行结果可以看出,malloc函数不会对开辟的内存区域的数据进行初始化,而calloc会将其初始化成0.
realloc
前面只提到了动态内存的分配和释放,但是动态内存还有一个关键的作用就是可以在运行的过程中调节这段内存空间的大小。这里介绍动态内存重分配函数realloc。
先来看看realloc函数的函数原型
memblock是一个void的指针,这个参数是需要重新分配的空间的指针
size是新空间的大小
realloc函数的返回值是一个void的指针,这个指针是指向新开辟的空间的起始地址的指针
开辟失败则返回NULL。
使用realloc函数调整动态内存空间
如果将一个函数的第一个参数,是指向动态内存的空间的起始地址的指针,那么这个函数的作用,就是将这个指针指向的动态内存的空间进行大小调整
int* p = malloc(40);
int* ptr = realloc(p, 80);
上述代码的作用是将p指向的那段动态内存的空间,从40字节的大小,变为80字节的大小。
也可以用这个函数将内存空间变小
int* p = malloc(40);
int* ptr = realloc(p, 20);
此时p指向的这段动态内存的空间由40字节变为20字节,而且这个多余的20字节的空间会被直接释放掉。
使用realloc函数分配动态内存空间
如果realloc函数的第一个参数是NULL,那么此时这个函数会随机分配一个大小合适的动态内存空间给你。这和malloc的作用是一致的
malloc(40);
realloc(NULL,40);
此时这两个函数的作用没有区别。