在本篇当中我们将对动态内存相关的知识进行学习,了解malloc,calloc等函数的作用以及学习如何使用这些函数,并且避免一些常见的动态内存中的错误,还有学习柔性数组是什么,有什么作用。接下来就开始本篇的学习吧!!!
1.为什么要有动态内存分配
在之前的学习中已经了解了利用数组来开辟连续的内存空间,但是数组的大小一旦确定了就不能修改了,有时候我们需要的空间大小在程序运行的时候才能知道,那数组的编译时开辟空间的⽅式就不能满足了,使用这时就要用到c语言动态内存开辟,让程序员自己可以申请和释放空间
同时动态内存所在的内存位置也是与局部变量不同的,局部变量的数组是在内存中的栈区,而对于动态内存都是在内存中的堆区
2.malloc与free
2.1 malloc
c语言中提供了一个用于内存开辟的函数
对于malloc这个函数作用是开辟内存空间,参数是需要开辟的内存空间大小,这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。如果开辟空间成功就返回该空间的起始地址,开辟失败就返回空指针
2.2 free
C语言提供了另外⼀个函数free,专门是用来做动态内存的释放和回收的
free的作用是对动态内存的回收和释放, 例如以上当我们使用malloc开辟内存空间时,不同于数组在出了作用域系统就会自己释放空间,malloc在申请后如果不释放即使之后一直未用到这块空间,除非整个程序结束数据都会一直保留在该内存块当中,这就会使得内存的利用率下降
注:如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的
3.calloc与realloc
3.1 calloc
C语⾔还提供了⼀个函数叫 calloc , calloc 函数也用来动态内存分配。
calloc的作用也是用来开辟内存空间,作用是开辟num个大小为size字节大小的内存块。不同于malloc,calloc的参数有两个,一个是需要开辟内存块的大小,另一个是需要开辟的内存块的数量
calloc的返回值也是所开辟的内存空间的起始地址。并且calloc在开辟完空间后会将空间内数据都初始化为0
在使用calloc申请内存空间后,当不再使用时也需要使用free将内存空间释放并将指针置为空指针
3.2 realloc
在之前了解了动态内存开辟的两个函数malloc与realloc,那么如果要对开辟的内存空间进行调整就需要用到realloc
realloc的作用是对原来已经开辟的内存空间进行调整。这个函数的参数有两个,第一个ptr是原来已经动态内存开辟的内存空间的起始地址,另一个size是调整后内存空间的大小
4. 常见的动态内存的错误
1 对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
例如在以上代码中使用malloc开辟内存空间时可能会开辟失败返回空指针NULL,这时不进行判断直接对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);
}
例如在以上代码当中使用malloc只开辟了10个大小为int的内存空间,而之后的for循环内最后
*(p+10)已经超出了以上内存空间的范围,这时就是在对内存的越界访问,这种行为是非法的
3.对非动态开辟内存使用free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
例如在以上代码中的变量a不是动态内存开辟的,所以就不能对变量a所在的内存空间用free进行释放
4.使用free释放一块动态开辟内存的一部分
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
例如在以上代码中对p++后p就不再指向内存块的起始地址,这时再使用free释放内存空间程序就会崩溃
5.对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
例如在以上代码中已经对malloc开辟的内存空间free一次,之后又free,并且之前也没将p置为空指针,这样程序就会崩溃
6.动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
例如在以上代码中在每次调用test函数时都使用malloc进行内存开辟,但每次都不对内存进行释放,这就会使得开辟后的内存空间一直占据在内存当中,程序一直执行开辟过的内存空间就再也找不到了,这就是内存泄漏。最终程序一直运行就会使得内存完全被占满,程序就会发生崩溃
切记:动态开辟的空间一定要释放,并且正确释放
5. 动态内存经典笔试题分析
5.1 题目1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test 函数会有什么样的结果?
在以上代码在中Test中调用了GetMemory函数,参数为变量str,所以这时为传值调用,因为形参是实参的一份临时拷贝,所以这时的p不再是原来的str,只是将str的内容拷贝到p内。之后用malloc开辟100字节空间给p,就会使p里面存放内存空间的起始地址,但这时str内还是存放NULL,所以这时使用strcpy将hello world拷贝到str内就是对空指针的使用,程序就会崩溃,同时malloc申请的内存空间一直没被释放还会造成内存泄漏
5.2 题目2:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test 函数会有什么样的结果?
在以上代码中调用GetMemory时返回的是栈空间的地址,这时在出GetMemory局部范围时p所指向的内存空间就会还给操作系统,这时p就变为野指针,输出结果就是随机的
5.3 题目3:
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test 函数会有什么样的结果?
在以上代码当中先将指针str初始化为NULL,之后调用GetMemory时是传址调用,所以不会再拷贝一份原来的变量,GetMemory内用二级指针char** p来接收&str,这时*p就是和使用str一样,所以使用malloc开辟100字节的内存空间后就将起始地址传给str,之后再用strcpy将hello拷贝到str内,打印str,到此都是没错误的,但最后没有对开辟的内存空间进行free释放,会造成内存泄漏
5.4 题目4:
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
请问运行Test 函数会有什么样的结果?
在以上代码中先使用malloc开辟了100字节的空间,并将起始地址传给str,再使用strcpy将hello拷贝到str的内存空间内,这时再free(str)后就存在问题,未将str置为空指针,这时再使用strcpy将world拷贝到str的内存空间内就形成对内存的越界访问,所以要将以上代码改为正确就要在free(str)后加上str=NULL
6. 柔性数组
也许你从来没有听说过柔性数组(flexible array)这个概念,但是它确实是存在的。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
例如:
struct st_type
{
int i;
int a[0];//柔性数组成员
};
有些编译器会报错无法编译可以改成:
struct st_type
{
int i;
int a[];//柔性数组成员
};
6.1 柔性数组的特点
• 结构中的柔性数组成员前⾯必须⾄少⼀个其他成员。
• sizeof 返回的这种结构大小不包括柔性数组的内存。
• 包含柔性数组成员的结构用malloc ()函数进⾏内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
例如
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
int main()
{
printf("%d\n", sizeof(type_a));//输出的是4
return 0;
}
6.2 柔性数组的使用
#include <stdlib.h>
#include <stdio.h>
struct S
{
int n;
int arr[];
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S) + 20*sizeof(int));
if(ps == NULL)
{
perror("malloc()");
return 1;
}
//使用这些空间
ps->n = 100;
int i = 0;
for (i = 0; i < 20; i++)
{
ps->arr[i] = i + 1;
}
//调整ps指向空间的大小
struct S* ptr = (struct S*)realloc(ps, sizeof(struct S) + 40 * sizeof(int));
if (ptr != NULL)
{
ps = ptr;
ptr = NULL;
}
else
{
return 1;
}
//使用
for (i = 0; i < 40; i++)
{
printf("%d ", ps->arr[i]);
}
//释放空间
free(ps);
ps = NULL;
return 0;
}
在以上代码还有什么方法可以实现同样的效果呢?
当结构体内的数组是指针时就可以写成以下形式
#include <stdlib.h>
#include <stdio.h>
struct S
{
int n;
int* arr;
};
int main()
{
struct S* ps = (struct S*)malloc(sizeof(struct S));
if (ps == NULL)
{
perror("malloc");
return 1;
}
int*tmp = (int*)malloc(20*sizeof(int));
if (tmp != NULL)
{
ps->arr = tmp;
}
else
{
return 1;
}
ps->n = 100;
int i = 0;
//给arr中的20个元素赋值为1~20
for (i = 0; i < 20; i++)
{
ps->arr[i] = i + 1;
}
//调整空间
tmp = (int*)realloc(ps->arr, 40*sizeof(int));
if (tmp != NULL)
{
ps->arr = tmp;
}
else
{
perror("realloc");
return 1;
}
//
for (i = 0; i < 40; i++)
{
printf("%d ", ps->arr[i]);
}
//释放
free(ps->arr);
ps->arr = NULL;
free(ps);
ps = NULL;
return 0;
}
6.3 柔性数组的优势
上述 代码1 和 代码2 可以完成同样的功能,但是 方法1 的实现有两个好处:
7. 总结C/C++中程序内存区域划分