动态内存函数的介绍 malloc和free malloc 和 free 都声明在 stdlib.h 头文件中。我们目前已知的开辟内存的方法
int val = 20 ; // 在栈空间上开辟四个字节 char arr [ 10 ] = { 0 }; // 在栈空间上开辟 10 个字节的连续空间 但是上述的开辟空间的方式有两个特点: 1. 空间开辟大小是固定的。2. 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配。
但是对于空间的需求,不仅仅是上述的情况。
有时候我们需要的空间大小在程序运行的时候才能知道,
那数组的编译时开辟空间的方式就不能满足了。这时候就只能试试动态存开辟了
-
malloc
void* malloc (size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针。如果开辟成功,则返回一个指向开辟好空间的指针。
如果开辟失败,则返回一个NULL指针,因此malloc的返回值一定要做检查。
返回值的类型是 void* ,所以malloc函数并不知道开辟空间的类型,具体在使用的时候使用者自己来决定。
如果参数 size 为0,malloc的行为是标准是未定义的,取决于编译器。
下面举例
#include
#include
int main()
{
//申请空间
int* ptr = (int*)malloc(40); //返回类型强转为 int*
int* p = ptr; //记录起始地址
if (p == NULL) //判断是否为空指针
{
perror("malloc");
return 1;
}
//打印新开辟的空间
int i = 0;
for (i = 0; i < 10; i++)
{
*p = i;
p++;
}
}
free
void free (void* ptr);
释放时必须是在内存空间的起始位置
free函数用来释放动态开辟的内存。
如果参数 ptr 指向的空间不是动态开辟的,那free函数的行为是未定义的。
如果参数 ptr 是NULL指针,则函数什么事都不做
当我们不释放动态申请的内存的时候
如果程序结束,动态申请的内存由 *** 作系统自动回收
但是如果程序不结束,动态内存是不会自动回收的,就会形成内存泄露的问题,free空指针啥也不发生
//释放空间
free(ptr);
ptr = NULL;
if (ptr != NULL)
{
*ptr = 100;
}
*ptr = 100;//err
return 0;
calloc
void* calloc ( size_t num , size_t size ); 函数的功能是为 num 个大小为 size 的元素开辟一块空间,并且把空间的每个字节初始化为 0 。与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全 0 。
#include
#include
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if(NULL != p)
{
//使用空间}
free(p);
p = NULL;
return 0; }
realloc
void* realloc ( void* ptr , size_t size ); realloc 函数的出现让动态内存管理更加灵活。有时会我们发现过去申请的空间太小了,有时候我们又会觉得申请的空间过大了,那为了合理的时候内存,我们一定会对内存的大小做灵活的调整。
那 realloc
函数就可以做到对动态开辟内存大小的调整。函数原型如下: ptr 是要调整的内存地址 size 调整之后新大小 返回值为调整之后的内存起始位置。
这个函数调整原内存空间大小的基础上,还会将原来内存中的数据移动到新 的空间 realloc 在调整内存空间的是存在两种情况: 情况 1 :原有空间之后有足够大的空间 情况 2 :原有空间之后没有足够大的空间 情况 1 当是情况 1 的时候,要扩展内存就直接原有内存之后直接追加空间,原来空间的数据不发生变化。
情况 2 当是情况 2 的时候,原有空间之后没有足够多的空间时,扩展的方法是:在堆空间上另找一个合适大小 的连续空间来使用。
这样函数返回的是一个新的内存地址
int main()
{
int*p = (int*)malloc(40);
if (p == NULL)
{
perror("malloc");
return 1;
}
//使用
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = i;//0 1 2 3 4 5 6 7 8 9
}
//空间不够,希望能放20个元素,考虑扩容
int*ptr = (int*)realloc(p, 80);
if (ptr != NULL)
{
p = ptr; //我们希望还是p来掌管这块新空间,先用ptr做个周转。
因为扩容失败也会返回空指针
}
//扩容成功了,开始使用
//不再使用,就释放
free(p);
p = NULL;
return 0;
}
常见的动态内存错误
对空指针解引用 *** 作
void test () { int * p = ( int * ) malloc ( INT_MAX / 4 ); * p = 20 ; // 如果 p 的值是 NULL ,就会有问题,造成空指针从而访问失败 free ( p ); } 如何避免: 记得对malloc返回值进行判空 *** 作。对动态内存开辟的越界访问记得最后进行free
free(p); p = NULL;
void test () { int i = 0 ; int * p = ( int * ) malloc ( 100 ); if ( NULL == p ) { exit ( EXIT_FAILURE ); } for ( i = 0 ; i <= 25 ; i ++ ) { * ( p + i ) = i ; // 当 i是25的时候越界访问 } free ( p ); } 对内存边界进行检查对非动态内存进行释放
void test () { int a = 10 ; int * p = & a ; free ( p ); } 编译器会对非动态内存释放使用free释放一块动态开辟内存的一部分
void test () { int * p = ( int * ) malloc ( 100 ); if(p == NULL) {对同一块内存空间的多次释放
return 1 }
for(int i=0;i<10;i++) { *p=i; p ++ ; } free ( p ); p = NULL; } 此时的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 ); } p是个局部变量,出了之后就被销毁,没有人记得申请空间的地址,无法释放。笔试题目造成内存泄漏,之后想释放也释放不了
void GetMemory ( char * p ) { p = ( char * ) malloc ( 100 ); //本身p是str的临时拷贝,p也是空指针,出了作用域,p被销毁。
} void Test ( void ) { char * str = NULL ; GetMemory ( str ); strcpy ( str , "hello world" ); //对空指针进行解引用,造成程序崩溃,内存泄漏 printf ( str ); //这种写法是将str首地址传过去进行打印 } int main() { Test() return 0;
} 输出:打印不出来
char * GetMemory ( void ) { char p [] = "hello world" ; //栈上开辟的空间,出了这个函数就被 *** 作系统回收 return p ; } void Test ( void ) { char * str = NULL ; str = GetMemory (); printf ( str ); } int main() { Test() return 0;
} 输出:烫烫烫 原因:str先是空指针,然后进入GetMemory函数,p创建了字符串,有了地址,出来作用域p所创建的字符串被销毁,空间已经不属于我们了,但p的地址被返回str,str通过这个地址再去访问的时候,就变成了野指针
void GetMemory ( char ** p , int num ) { * p = ( char * ) malloc ( num ); } void Test ( void ) { char * str = NULL ; GetMemory ( & str , 100 ); strcpy ( str , "hello" ); printf ( str ); } int main() { Test() return 0;
} 输出:hello 申请的空间忘记释放
void Test ( void ) { char * str = ( char * ) malloc ( 100 ); strcpy ( str , "hello" ); free ( str ); if ( str != NULL ) { strcpy ( str , "world" ); printf ( str ); //野指针。C/C++程序的内存开辟free后空间已经回收了,但str还记得空间的地址。
想在复制上world造成非法访问
} } int main() { Test() return 0;
}
C/C++ 程序内存分配的几个区域: 1. 栈区( stack ):在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结 束时这些存储单元自动被释放。柔性数组栈内存分配运算内置于处理器的指令集中,效率很高,但是
分配的内存容量有限。栈区主要存放运行函数而分配的局部变量、函数参数、返回数据、返
回地址等。2. 堆区( heap ):一般由程序员分配释放, 若程序员不释放,程序结束时可能由 OS( *** 作系统) 回收 。
分配方式类似于链表。
3. 数据段(静态区)( static )存放全局变量、静态数据。
程序结束后由系统释放。
4. 代码段:存放函数体(类成员函数和全局函数)的二进制代码。
有了这幅图,我们就可以更好的理解在《 C 语言初识》中讲的 static 关键字修饰局部变量的例子了。
实际上普通的局部变量是在 栈区 分配空间的,栈区的特点是在上面创建的变量出了作用域就销毁。
但是被 static 修饰的变量存放在 数据段(静态区) ,数据段的特点是在上面创建的变量,直到程序结束才销毁 所以生命周期变长。
C99 中,结构中的最后一个元素允许是未知大小的数组,这就叫做柔性数组成员柔性数组的特点struct S1
{
int num;
double d;
int arr[];//柔性数组成员
};
struct S2
{
int num;
double d;
int arr[0];//柔性数组成员
结构中的柔性数组成员前面必须至少一个其他成员。sizeof 返回的这种结构大小不包括柔性数组的内存。
包含柔性数组成员的结构用 malloc () 函数进行内存的动态分配,并且分配的内存应该大于结构的大小,以适应柔性数组的预期大小。
上图是预留了10个元素给柔性数组
struct S3
{
int num;//4
int arr[];//柔性数组成员
};
int main()
{
struct S3* ps = (struct S3*)malloc(sizeof(struct S3)+40); //“+40”预留十个整型数组大小
if (ps == NULL)
{
perror("malloc");
return 1;
}
ps->num = 100;
int i = 0;
for (i = 0; i < 10; i++)
{
ps->arr[i] = i;
}
for (i = 0; i < 10; i++)
{
printf("%d ", ps->arr[i]);
}
柔性数组的优势
上述还可以不用柔性数组来实现
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; }
这个是是malloc开辟两次空间从而实现。
但是第一种代码更优。
原因如下:
第一个好处是:方便内存释放 如果我们的代码是在一个给别人用的函数中,你在里面做了二次内存分配,并把整个结构体返回给用户。用户调用free
可以释放结构体,但是用户并不知道这个结构体内的成员也需要 free ,所以你不能指望用户来发现这个事。所以,如果我们把结构体的内存以及其成员要的内存一次性分配好了,并返回给用户一个结构体指针,用户做一次free
就可以把所有的内存也给释放掉。第二个好处是:这样有利于访问速度 . 连续的内存有益于提高访问速度,也有益于减少内存碎片
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)