1.为什么存在动态内存分配
int main ( )
{
int a = 0 ;
char arr[ ] = "abcde" ;
return 0 ;
}
空间开辟大小是固定的。 数组在申明的时候,必须指定数组的长度,它所需要的内存在编译时分配
而有时候我们我们需要的空间大小在程序运行起来才能知道,这时候就需要动态开辟的内存了
2.动态内存开辟的函数
1.malloc
由于malloc不清楚将来申请的空间的类型是什么,所以不能确定指针类型,那么就返回一个空指针 参数为申请的字节数 我们可以将malloc的返回值强制类型转换成我们想要的指针类型
int main ( )
{
char * p = ( cahr* ) malloc ( 10 * sizeof ( char ) ) ;
if ( p == NULL )
{
perror ( "malloc" ) ;
return 1 ;
}
free ( p) ;
p = NULL ;
}
2.calloc
参数分别为元素个数,每个元素大小 可以将动态开辟的空间初始化为0
int main ( )
{
int * p = ( int * ) calloc ( 10 , sizeof ( int ) ) ;
if ( p == NULL )
{
perror ( "calloc" ) ;
return 1 ;
}
free ( p) ;
p = NULL ;
}
3.realloc
int main ( )
{
int * p = ( int * ) malloc ( sizeof ( int ) * 10 ) ;
if ( p == NULL )
{
perror ( "calloc" ) ;
return 1 ;
}
int * str = ( int * ) realloc ( p, sizeof ( int ) * 20 ) ;
if ( str == NULL )
{
perror ( "realloc" ) ;
return 1 ;
}
p = str;
free ( p) ;
p = NULL ;
return 0 ;
}
当所开辟的空间小了,用realloc来调整动态内存开辟的空间,使其变大
因为返回值可能为空指针,所以realloc的返回值不能直接用p来接收,需要先进行判断
realloc在调整空间大小时有两种情况 情况一 malloc申请的空间后面有足够大的空间用来调整空间,realloc会在空间后追加一块新的空间 情况二
malloc申请的空间后面没有足够大的空间用来调整空间,realloc寻找一块新的空间供其开辟,并且把原来的数据拷贝到新的内容,返回新的空间的地址 由此得出realloc开辟的空间可能返回旧地址,也可能返回新地址,所以要将realloc返回的指针传给p
4.free
释放开辟动态内存 当free的参数传的是NULL时,free不会做任何事
3.常见的动态内存错误
1.对NULL解引用
void test ( )
{
int * p = ( int * ) malloc ( sizeof ( int ) * 4 ) ;
* p = 20 ;
free ( p) ;
}
2.对动态内存开辟空间的越界访问
void test ( )
{
int i = 0 ;
int * p = ( int * ) malloc ( 10 * sizeof ( int ) ) ;
if ( NULL == p)
{
return ;
}
for ( i= 0 ; i<= 10 ; i++ )
{
* ( p+ i) = i;
}
free ( p) ;
}
3.对非动态内存开辟的空间进行free
void test ( )
{
int a = 10 ;
int * p = & a;
free ( p) ;
}
4.使用free释放动态内存的一部分
void test ( )
{
int * p = ( int * ) malloc ( 10 ) ;
p++ ;
free ( p) ;
}
5.对同一块空间进行多次释放
void test ( )
{
int * p = ( int * ) malloc ( 100 ) ;
free ( p) ;
free ( p) ;
}
6.动态内存开辟忘记释放(内存泄漏)
void test ( )
{
int * p = ( int * ) malloc ( 100 ) ;
if ( NULL != p)
{
* p = 10 ;
}
}
int main ( )
{
test ( ) ;
return 0 ;
}
此时p从函数出来时被回收,也就没人记得该内容的地址了,导致内存泄漏
4. 柔性数组
struct s
{
int a;
int arr[ ] ;
} ;
结构体中柔性数组前面必须至少有一个成员,否则不知道结构体大小多大,不能为它开辟空间 sizeof计算结构体大小不包括柔性数组大小 包含柔性数组的结构体要用malloc动态开辟内存空间,并且要大于结构体大小来适应柔性数组的预期大小
1. 使用柔性数组动态内存开辟与不使用柔性数组动态内存开辟
struct s
{
int a;
int arr[ ] ;
} ;
int main ( )
{
struct s * p = ( struct s * ) malloc ( sizeof ( struct s ) + sizeof ( int ) * 10 ) ;
if ( p == NULL )
{
perror ( "malloc" ) ;
return 1 ;
}
p-> a = 100 ;
int i = 0 ;
for ( i = 0 ; i < 10 ; i++ )
{
* ( p-> arr+ i) = i;
}
for ( i = 0 ; i < 10 ; i++ )
{
printf ( "%d " , * ( p-> arr+ i) ) ;
}
printf ( "\n" ) ;
struct s * str = ( struct s * ) realloc ( p, sizeof ( struct s ) + sizeof ( int ) * 20 ) ;
if ( str == NULL )
{
perror ( "str" ) ;
return 1 ;
}
p = str;
for ( i = 10 ; i < 20 ; i++ )
{
* ( p-> arr + i) = i;
}
for ( i = 0 ; i < 20 ; i++ )
{
printf ( "%d " , * ( p-> arr + i) ) ;
}
free ( p) ;
p = NULL ;
return 0 ;
}
struct s
{
int a;
int * arr;
} ;
int main ( )
{
struct s * p = ( struct s * ) malloc ( sizeof ( struct s ) ) ;
int * pc = ( int * ) malloc ( sizeof ( int ) * 10 ) ;
if ( p == NULL )
{
perror ( "p:malloc" ) ;
return 1 ;
}
if ( pc == NULL )
{
perror ( "pc:malloc" ) ;
return 1 ;
}
p -> arr = pc;
int i = 0 ;
for ( i = 0 ; i < 10 ; i++ )
{
* ( p-> arr + i) = i;
}
for ( i = 0 ; i < 10 ; i++ )
{
printf ( "%d " , * ( p-> arr + i) ) ;
}
int * str = ( int * ) realloc ( p-> arr, 20 * sizeof ( int ) ) ;
if ( str == NULL )
{
return 1 ;
}
p-> arr = str;
printf ( "\n" ) ;
for ( i = 10 ; i < 20 ; i++ )
{
* ( p-> arr + i) = i;
}
for ( i = 0 ; i < 20 ; i++ )
{
printf ( "%d " , * ( p-> arr + i) ) ;
}
free ( p-> arr) ;
p-> arr = NULL ;
free ( p) ;
p = NULL ;
return 0 ;
}
2.使用柔性数组的优点
使用柔性数组只是用了一次malloc,free,不使用柔性数组使用了两次malloc,free 在堆区上进行动态内存开辟时,开辟的空间并不是一块紧接着一块,它们之间的空隙成为内存碎片,由于内存碎片所占空间较小,被使用的机会也会很少,这就导致了空间的浪费,内存使用率下降,所以要尽量少的使用malloc 使用更多次的malloc,free,空间维护难度加大,更容易出错