目录
为什么存在动态内存管理
1.可以申请大块内存,进行较为大型的应用
2.可以在
程序运行期间进行申请
,可以更灵活的使用内存空间。
(申请空间用malloc()函数,必须是程序运行起来才能调用,编译的过程早就过了)
如何进行空间管理?
通过malloc函数申请空间和使用,用free函数释放空间
常见细节:
1.整体申请,整体释放。
2.如果申请内存不归还,会造成
内存泄漏
。
内存泄漏
:一种严重的问题,会造成可用内存越来越少。
进程退出了,内存泄漏问题就不在了。
程序分为:一运行立马结束的程序和常驻程序(死循环的,不会结束的程序),常驻程序容易造成内存泄漏。
3.申请有大小,free没有告诉我们应该释放多少个字节?那么free如何知道要释放多少呢?目前free参数只能知道从哪里开始释放。
申请的时候,实际申请的空间,会比你要的空间大一些,大出来的部分用来保存本次申请的“
原信息”
原信息:属性信息(对应堆空间的大小等等)
4.堆空间适合大块空间的申请(因为还需要多申请空间保存属性信息)
5.free究竟干了什么工作?
取消指针内部保存的地址和堆空间的关系,但指针的值是不变的,所以
建议在用free释放空间之后,将指针置为NULL。
动态内存函数的介绍
malloc和free
C语言提供的动态内存开辟函数
void* malloc(size_t size);
这个函数向内存申请一块
连续可用
的空间,并返回指向这块空间的指针。
如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个
NULL
指针,因此
malloc
的返回值一定要做检查。
返回值的类型是
void*
,所以
malloc
函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数
size
为
0
,
malloc
的行为是标准是未定义的,取决于编译器。(vs2013可以)。
C
语言提供了另外一个函数
free
,专门是用来做动态内存的释放和回收的,函数原型如下:
void free (void* ptr);
free
函数用来释放动态开辟的内存。
如果参数
ptr
指向的空间不是动态开辟的,那
free
函数的行为是未定义的。
如果参数
ptr
是
NULL
指针,则函数什么事都不做。
calloc
void* calloc(size_t num,size_t size);

realloc
realloc函数可以对已经申请内存的大小进行调整。
void* realloc (void* ptr, size_t size);
ptr 是要调整的内存地址 ;size 调整之后新大小 ;返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到
新
的空间。
realloc
在调整内存空间的是存在两种情况:
常见的动态内存错误
一、对NULL指针的解引用操作
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
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);
}
三、对非动态开辟的空间进行free释放
void test()
{
int a = 10;
int *p = &a;
free(p);//ok?
}
四、使用free释放内存空间的一部分(必须整体申请,整体释放)
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
比
五、对同一块动态内存多次释放
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
六、
动态开辟内存忘记释放(内存泄漏)
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}
}
int main()
{
test();
while(1);
}
笔试题
题目
1
:

2.题目二
char *GetMemory(void) {
char p[] = "hello world";
return p;
}
void Test(void) {
char *str = NULL;
str = GetMemory();
printf(str);
}
函数在栈上开辟,函数调用完之后被释放了,所以没有返回值。
注:计算机之中释放空间不会将数据清空(设置数据无效),所以str还是指向hellow world
由上图可知,函数已经执行完了,但是hellow world还是存在,那么为什么不输出str呢?
那是因为printf也是函数,被调用也是需要在栈上开辟空间的,所以他会将原本保存hellow world数据的空间覆盖,所以无法输出。
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);
}
没有判断参数的合法性,并且用malloc申请的空间没有释放(有可能会造成内存泄漏!!)。
4.题目四:
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
free对空间进行释放之后,str指针不等于NULL,但是空间却不可以用了,所以一般建议在进行空间释放之后,将指针置为NULL。
柔性数组
也许你从来没有听说过
柔性数组(
flflexible array
)
这个概念,但是它确实是存在的。
C99
中,结构中的最
后一个元素允许是未知大小的数组,这就叫做『柔性数组』成员。
typedef struct st_type
{
int i;
int a[0];//柔性数组成员 报错的话将其改为int a[];
}type_a;
柔性数组的特点:
结构中的柔性数组成员前面必须至少一个其他成员。
sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用
malloc ()
函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小
例如:
typedef struct st_type
{
int i;
int a[0];//柔性数组成员
}type_a;
printf("%d\n", sizeof(type_a));//输出的是4
柔性数组的使用
//代码1
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);
这样柔性数组成员
a
,相当于获得了
100
个整型元素的连续空间。
柔性数组的优势
上述代码也可以写成:
//代码2
typedef struct st_type
{
int i;
int *p_a; }type_a;
type_a *p = 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;
上述
代码
1
和
代码
2
可以完成同样的功能,但是
方法
1
的实现方便内存释放
如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用
户调用
free
可以释放结构体,但是用户并不知道这个结构体内的成员也需要
free
,所以你不能指望用户来发
现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体
指针,用户做一次
free
就可以把所有的内存也给释放掉。