在C语言中,内存分为几个不同的区域,包括栈区(Stack),堆区(Heap),静态区(Static)、常量区(Constant Area)和代码区(Code Area)。
栈区(Stack):
- 存储局部变量和函数的调用信息。
- 自动分配和释放内存,遵循"先进后出"的原则。
- 生命周期与函数调用相关,当函数执行完成时,其栈上的局部变量会被自动销毁。
- 通常用于存储函数的局部变量、函数参数和返回地址等。
堆区(Heap):
- 用于动态内存分配,程序员手动分配和释放内存。
- 生命周期不受函数调用的限制,需要手动释放内存以防止内存泄漏。
int val = 20;//在栈空间上开辟四个字节
char arr[10] = {0};//在栈空间上开辟10个字节的连续空间
了解完栈区后,上述的代码开辟空间的方式有两个特点:
但是对于空间的需求,有时候只有在程序运行起来的时候才知道需要多少,那么数组编译时开辟的空间方式就不能满足需求了。于是,C语言引入了动态内存开辟,将这些数据放入堆区中,让程序员自己申请和释放空间,就比较灵活了。
malloc函数的原型如下:
void* malloc (size_t size);
这个函数向内存申请⼀块连续可⽤的空间,并返回指向这块空间的指针。
C语言提供了一个专门用来做动态内存释放和回收的函数free,函数原型如下:
void free (void* ptr);
free函数⽤来释放动态开辟的内存。
malloc和free都声明在stdlib.h头文件中。
如何做到存放十个整型数据呢?看看下面的案例:
因为malloc函数返回一个void类型的指针,所以需要强制转换成指定的指针类型。之后需要加入判断,因为malloc开辟空间时哟而有可能失败,会返回空指针。如果不加以判断,对空指针进行使用,这种情况十分危险。接着就可以正常使用该空间,用一个for循环初始化该40个字节的内容,接着还可以使用for循环打印初始化的内容。最后就是释放申请的空间,释放后ptr还是指向这块不能使用空间,ptr成为了野指针,必须重置为空指针(NULL)。还可以这样动态开辟空间:
此时,malloc用法一致。到判断这一环节,使用了strerror字符串内置函数。strerror可以用来打印程序异常时发生了何种错误的信息,strerror内的参数时错误码,每个程序运行完后都会返回一个数值,但是我们无法解析错误码代表的错误的信息,就可以用strerror转换错误码为一串字符。而错误码会放在errno之中,errno是一个全局变量,用来记录发生错误的标识符。strerror和errno需要分别用string.h和errno.h来声明。
C语言还提供了一个函数交calloc,擦了咯从函数也用来动态内存分配。原型如下:
void* calloc (size_t num, size_t size);
举个例⼦:
输出的结果:
所以如果我们对申请的内存空间的内容要求初始化,那么可以很⽅便的使⽤calloc函数来完成任务。
函数原型如下:
void* realloc (void* ptr, size_t size);
情况1
当是情况1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发⽣变化。
情况2
当是情况2 的时候,原有空间之后没有⾜够多的空间时,扩展的⽅法是:在堆空间上另找⼀个合适⼤⼩的连续空间来使⽤。这样函数返回的是⼀个新的内存地址。
由于上述的两种情况,realloc函数的使⽤就要注意⼀些。
代码一错误,当发生情况2的时候,如果realloc无法分配所需的内存空间,它将返回NULL,并且原始的指针ptr将丢失,这可能导致内存泄漏。因为ptr直接指向realloc返回的新内存,如果realloc失败并返回NULL,原来的内存块将丢失,无法再次释放。所以代码2的做法更加稳妥。
忘记释放不再使⽤的动态开辟的空间会造成内存泄漏。
谨记:动态开辟的空间⼀定要释放,并且正确释放。
分析一下四个Test函数运行之后的结果分别是什么。
GetMemory
中返回了一个局部变量的地址。局部变量p
是在栈上分配的,当函数执行完毕后,该内存空间将被释放,所以无法打印任何东西,。而printf函数内的参数是“const char * format, ...”,就是一个字符指针,代表打印字符的首地址,str'就是一个字符指针,这样写没有问题。所以解决方法是将p定义为静态变量或者使用动态内存分配函数分配内存。C99 中,结构中的最后⼀个元素允许是未知⼤⼩的数组,这就叫做柔性数组成员。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
有些编译器会报错⽆法编译可以改成:
typedef struct st_type
{
int i;
int a[];//柔性数组成员
}type_a;
看看下面的例子:
上述的 type_a 结构也可以设计为下⾯的结构,也能完成同样的效果。
上述的 type_a 结构也可以设计为下⾯的结构,也能完成同样的效果。
第⼀个好处是:⽅便内存释放
如果我们的代码是在⼀个给别⼈⽤的函数中,你在⾥⾯做了⼆次内存分配,并把整个结构体返回给⽤⼾。⽤⼾调⽤free可以释放结构体,但是⽤⼾并不知道这个结构体内的成员也需要free,所以你不能指望⽤⼾来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存⼀次性分配好了,并返回给⽤⼾⼀个结构体指针,⽤⼾做⼀次free就可以把所有的内存也给释放掉。
第⼆个好处是:这样有利于访问速度.
连续的内存有益于提⾼访问速度,也有益于减少内存碎⽚。(其实,我个⼈觉得也没多⾼了,反正你跑不了要⽤做偏移量的加法来寻址)
动态内存管理是C语言给程序员自己手动分配内存的工具,在日后使用频率高。这是个很重要的知识点,也需要多多练习,分析代码,才能掌握。话不多说,学起来吧。多多重复,百炼成钢!
创作不易,如果喜欢这篇文章的话,请留下你宝贵的三连,你的支持是我最的的动力!!!