浅析动态内存

我们已经掌握的内存开辟方式有:

int val = 20;//在栈空间上开辟四个字节

char arr[10] = {0};//在栈空间上开辟10个字节的连续空间

但是是上述的开辟空间的方式有两个不太方便的点:

  1. 空间开辟大小是固定的。

  2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。(c99标准中,新增了可变长度数组:Variable-length array (VLA)

但是对于空间的需求,不仅仅是上述的情况。有时候我们需要的空间大小在程序运行的时候才能知道, 那数组的编译时开辟空间的方式就不能满足了。 这时候就只能试试动态内存开辟了。

malloc()和free()

C语言提供了一个动态内存开辟的函数:

void* malloc (size_t size);

该函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。

如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。

返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。

通常,malloc()和free()配套使用。

void free (void* ptr);

free()函数的参数是之前malloc()返回的地址,该函数释放之前malloc()分配的内存。

因此,动态分配的存储期从调用malloc()分配内存到调用free()释放内存为止。设想这俩函数管理着一个内存池,每次调用malloc()分配内存给程序使用,再调用free()把内存还回去,这样可以重复使用这些内存。

此外,我们不能用free()释放通过其他方式(比如声明一个数组)分配的内存。

free()与malloc()的原型都在stdlib.h头文件中。

free()的重要性

静态内存的数量在编译时是固定的,在程序运行期间也不会改变。自动变量使用的内存数量在程序执行期间自动增加或减少。

但是动态分配的内存数量只会增加,除非使用free()进行释放。

#include <stdio.h>
int main()
{
 int* ptr = NULL;
 ptr = (int*)malloc(num*sizeof(int));
 if(NULL != ptr)//判断ptr指针是否为空
 {
 int i = 0;
 for(i=0; i<num; i++)
 {
 *(ptr+i) = 0}
 }
 free(ptr);//释放ptr所指向的动态内存
 ptr = NULL;//是否有必要?
 return 0;
}

如上代码,有三个问题:

1.if语句是否有必要?

答案自然是肯定的,前面我们说过如果开辟失败会返回一个空指针,我们用户是不可以去操作空指针去做一些事情的,因此是需要检验一下返回指针是否为空。

2.free()的必要?

假设我们不释放此时创建的动态内存,那么在堆区就会存在无法再访问及无法再利用的一块内存。这类问题叫内存泄漏(memory leak)。利用free()释放后可以有效避免这类问题。

3.释放后指针赋值为空的必要?

释放内存后,该指针仍然指向那块内存,但是内容已经不复存在,如果我们此时一个不小心再次利用该指针,就会出现非法访问的问题,反正它也没用了,保险起见直接赋为空指针,方便后续的使用。

calloc()

void* calloc (size_t num, size_t size);

分配内存还可以使用calloc(),该函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为0。

其与函数 malloc() 的区别只在于 calloc() 会在返回地址之前把申请的空间的每个字节初始化为全0

所以如何我们对申请的内存空间的内容要求初始化,那么可以很方便的使用calloc函数来完成任务。

free()也可以用于释放calloc()分配的内存。

ralloc()

有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的使用内存,我们一定会对内存的大小做灵活的调整。那 realloc() 函数就可以做到对动态开辟内存大小的调整。

void* realloc (void* ptr, size_t size);

这个函数中,ptr是需要调整的内存地址,size为调整之后的新的大小,其返回值为调整之后的内存起始位置。

需要注意的是,此函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新的空间

很好理解,当原内存后面的内存大小比我想调整的大小大,即返回原来的起始地址即可。但如果原内存后面的内存大小不够我想调整的大小,扩展的方法是:在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址

注意:

  • 用 malloc()、calloc()、realloc() 分配成功的指针才能被 realloc() 函数接受。

  • 成功分配内存后 ptr 将被系统回收,一定不可再对 ptr 指针做任何操作,包括 free();相反的,可以对 realloc() 函数的返回值进行正常操作。

  • 分配成功返回新的内存地址,可能与 ptr 相同,也可能不同;失败则返回 NULL。

当然,动态分配内存自然是一件看起来相对高大上的事情,但高回报往往意味着高风险,动态内存如果利用不当的话,会出现很多难以预料的错误。

常见的动态内存错误

1.对NULL指针的解引用操作

自不必说,注意动态内存如果开辟失败返回空指针,一定要多多留意不可对空指针解引用。

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的时候越界访问,error!!
 }
 free(p);
}

动态开辟多大的内存就分配给你多大,就不要去强行再越界了,不够咱再要呗,也不至于去抢。

3.对非动态开辟内存使用free释放

void test()
{
 int a = 10;
 int *p = &a;
 free(p);//error!!!!!
}

free()只会释放动态开辟的内存,不可以强行释放静态内存。

4 使用free释放一块动态开辟内存的一部分

void test()
{
 int *p = (int *)malloc(100);
 p++;
 free(p);//p不再指向动态内存的起始位置
}

free()没你想象的那么厉害,不能把内存取一段出来把它释放了其余的不动,咱要释放就一块释放了,因此给free()的地址一定是内存起始的地址。

5 对同一块动态内存多次释放

void test()
{
 int *p = (int *)malloc(100);
 free(p);
 free(p);//重复释放,error!!!
}

一块内存释放了就没了,再释放就会出错。

6 动态开辟内存忘记释放(内存泄漏)

之前举过例子了,切记切记:动态开辟的空间一定要释放,并且正确释放 。

柔性数组

很陌生的概念,C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
//或者:
typedef struct st_type
{
 int i;
 int a[];//柔性数组成员
}type_a;

特点及使用

结构中的柔性数组成员前面必须至少一个其他成员。

sizeof 返回的这种结构大小不包括柔性数组的内存。

typedef struct st_type
{
 int i;
 int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));

输出结果为:

4

包含柔性数组成员的结构用malloc ()函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。

比如这样:

int i = 0;
type_a *p = (type_a*)malloc(sizeof(type_a)+100*sizeof(int));
//业务处理
p->i = 100;
for(i=0; i<100; i++)
{
 p->a[i] = i;
}
free(p);

一些优势

上边那个代码也可以这样写:

typedef struct st_type
{
 int i;
 int *p_a;
}type_a;
type_a *p = (type_a *)malloc(sizeof(type_a));
p->i = 100;
p->p_a = (int *)malloc(p->i*sizeof(int));

//业务处理
for(i=0; i<100; i++)
{
 p->p_a[i] = i;
}
//释放空间
free(p->p_a);
p->p_a = NULL;
free(p);
p = NULL;

多少是带点优势的,如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free可以释放结构体,但是用户并不知道这个结构体内的成员也需要free,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好 了,并返回给用户一个结构体指针,用户做一次free就可以把所有的内存也给释放掉。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值