动态内存管理
malloc函数
void* malloc (size_t size);
- 参数为要申请空间的大小,返回值为void *,具体使用在于操作者
- 如果申请成功会返回空间的起始位置,
- 如果申请失败会返回空指针null
free函数
void free (void* ptr);
free函数是专门用来做动态内存释放和回收的
- 如果参数ptr指向的空间不是动态开辟的,那么free的行为是未定义的
- 如果ptr为null,函数什么都不做
calloc函数
void* calloc (size_t num, size_t size);
- 函数功能是开辟num个大小为size的空间,并且把每个空间的字节初始化成0
- 与函数malloc的区别是,calloc在返回指针前会把清空目标内存存储的值
realloc函数
- realloc让动态内存管理更加灵活
- 有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时
候内存,我们一定会对内存的大小做灵活的调整。那 realloc 函数就可以做到对动态开辟内存大小
的调整
void* realloc (void* ptr, size_t size);
- ptr为要调整的指针
- size为重新申请空间的大小
- 返回值为调整后内存的起始位置
- 这个函数在在调整的时候会出现两种情况
- 1.原内存空间后内存足够大,直接追加开辟
- 2.原内存空间后内存不够大,将原内存的数据迁移到新开辟的一块空间中,然后将原来的内存free,返回新内存的空间
常见的内存错误
1.对NULL的解引用
int main(){
char *p=(char *)malloc(__INT_MAX__*4);
*p = 1;
printf("%d",p);
}
malloc函数如果申请内存失败会返回Null,所以在使用动态申请的内存之前,一定要进行判断。
2.对动态开辟空间的越界访问
int main() {
int *p = (int *)calloc(10,4);
for (int i = 0; i < 11; i++)
{
*(p + i) = i;
}
free(p);
/*test();*/
}
3.对非动态开辟内存使用free释放
int main(){
int arr[]={1,2,3,4,5};
free(arr);
}
4.对同一块动态内存多次释放
int main(){
char *p = malloc(5);
free(p);
free(p);
}
free函数不会改变p指针存储的地址,只会释放p指针指向内存存储的值,所以为了避免多次释放,在free后要将指针置为Null,free(Null)不会出现错误。
5.内存泄漏
void mmalloc(){
char *p = malloc(1);
}
int main(){
mmalloc();
}
在调用方法的时候动态申请了内存,方法结束没有释放那块内存,那就永远找不到那块内存了,那块内存的空间也无法释放,所以在函数里申请内存,函数的返回值必须为申请内存的地址。
几个经典的笔试题
题目1
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
p是形式参数,p的值是str的临时拷贝,所以给p开辟空间只是给形参开辟了一块空间,当函数结束,形参就会被销毁,这块空间就无法释放,也无法给str赋值。
题目2
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
函数中的变量都在堆空间,函数结束时自动销毁。在函数内创建了数组p,随后将p的地址返回。但是函数结束时,变量p的空间就会被回收,所以p指向的是未知的。
题目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);
}
最后没有free内存和将指针置为null
题目4
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
即使free掉空间,也是改变指针指向那块区域里的值,而不是改变指针指向。
C/C++程序内存的开辟
- 函数内的变量,形参都在栈开辟
- 用过malloc等手动申请的内存都在堆空间
- 全局数据和静态数据存储在数据段
柔性数组
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员
例如
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
特点
- 结构中的柔性数组成员前面必须至少一个其他成员。
- sizeof 返回的这种结构大小不包括柔性数组的内存。
- 包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大
小,以适应柔性数组的预期大小
//code1
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
int main(){
type_a *p = (type_a *)malloc(sizeof(type_a)+5*sizeof(int));
p->i = 3;
for (int i = 0; i < 5; i++)
{
p->a[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ",p->a[i]);
}
free(p);
p=null;
}
开辟的空间除了结构体就是柔性数组。
可以使用指针模拟柔性数组
typedef struct st_type2
{
int i;
int *a;//使用指针模拟数组
}type_b;
int main(){
type_b *p = malloc(sizeof(type_b));
p->i = 100;
p->a = malloc(5*sizeof(int));
for (int i = 0; i < 5; i++)
{
p->a[i] = i;
}
for (int i = 0; i < 5; i++)
{
printf("%d ",p->a[i]);
}
free(p->a);
p->a = NULL;
free(p);
p = NULL;
return 0;
}
与柔性数组的区别
- 结构体的大小还包括指针的大小,而柔性数组的结构体是不包含柔性数组的大小的。
- 指针申请的空间需要手动释放,且与结构体所在的空间不连续。