前言:
c提供了几个动态内存分配的函数,包含在 stdlib.h 头文件中:
函数原型如下:
void * malloc ( size_t size );
void free ( void *pointer );
malloc 的参数是所需分配内存的字节数byte,size_t 是无符号数(>=0),对于每个malloc返回的指针都要进行检查(是否为NULL)
对于有边界对齐要求的机器,malloc返回的内存起始位置始终能够满足对边界对齐要求最严格的类型的要求
void * calloc ( size_t num_elements,, size_t element_size );
void realloc ( void *ptr, size_t new_size );
malloc和calloc之间主要区别是:后者在返回指向内存的指针之前把它初始化为0。但如果程序只是想把一些值存储到数组中,这个初始化过程纯属浪费时间。
calloc和malloc之间另一个比较小的区别是:它们请求内存数量的方式不同。 calloc包括所需元素数量和每个元素的byte数。
使用了realloc之后,就不能再使用指向旧内存的指针,而是应该改用realloc所返回的新指针。
如果realloc函数的第一个参数是NULL,那么它的行为就和malloc一样了。
常见的动态内存错误:
使用动态内存分配时,常常出现错误:对NULL指针进行解引用(*p,需要判断返回的指针是否有效),对分配内存进行操作时越界,释放并不是动态分配分配的内存,试图释放一块动态分配的内存的一部分,一块动态内存被释放之后被继续使用。
最常见的错误:忘记检查所请求的内存是否成功分配。
第二大错误:操作内存时超过了分配内存的边界。比如,得到的是长度25的数组,而访问小标值小于0或者大于24导致两种问题,
第一种问题:访问的内存可能保存了其他变量的值,修改后破坏变量,
这种bug很难被发现!
第二种问题:在malloc和free的实现中,它们以链表的形式维护可用的内存池,对分配内存之外的区域进行访问可能破坏这个链表,可能产生异常,终止程序。
写一个不易发生错误的内存分配器:
接口:alloc.h
#include<stdlib.h>
#define malloc // do not invoke malloc directory
#define MALLOC(num,type) (type *)alloc( (num) * sizeof(type) )
extern void *alloc( size_t size );
#include<stdio.h>
#include<stdlib.h>
#include"alloc.h"
#undef malloc
//undo the malloc declaration before, so we could use malloc again
void *alloc( size_t num )
{
void *new_mem;
/*
* request the mem, test whether it it success
*/
new_mem = malloc( num );
if( new_mem == NULL ){
printf("out of memory\n");
exit(1);
}
return new_mem;
}
使用错误检查分配器 a_client.c
#include<stdio.h>
#include<stdlib.h>
#include"alloc.h"
int *function( size_t num )
{
int *new_mem;
/*
* acquire memory
*/
new_mem = MALLOC( num, int );
return new_mem;
}
int main( int argc, char *argv[] )
{
int *p = function( 20 );
for( int i = 0; i < 20; ++i, p++ ){
*p = i+1;
printf("the number is no:%d\n", *p);
}
exit( 0 );
}
总结:
使用动态内存分配失败时候,我们首先要检查是否访问了分配内存之外的区域!
传递给free的指针必须是从malloc,realloc,calloc函数返回的指针。若是让free释放并非动态分配的内存可能导致程序立即终止或者异常。
动态分配的内存一定要整块释放,但是使用 realloc 函数可以缩小一块动态分配的内存,有效释放它尾部的部分内存。
注意,不要访问被free释放过的内存。
特别是,对一个指向动态分配的内存的指针进行了复制,而且这个指针的拷贝散布在程序的各个地方。我们无法保证当你使用其中一个指针时它所指向的内存是否已经被另外一个指针释放。
另一方面,你必须确保程序中所有使用这块内存的地方在这块内存被释放之前停止对于它的使用!
内存分配实例:
补充一个知识,stdlib.h 自带的快速排序,qsort
原型:
void qsort{
void *base,
size_t num,
size_t width,
int (__cdecl *compare)( const void *, const void * )
};
可以这么写comp:
int comp( const void *a, const void *b )
{
return *(int *)a - *(int *)b; //小到大排序,反过来就是大到小排序
}
int compare_integers( void const *a, void const *b )
{
register int const *pa = a;
register int const *pb = b;
return *pa > *pb ? 1 : *pa < *pb ? -1 : 0; // cool !
}
eg:排序一列整形
动态分配空间,再进行排序
#include<stdio.h>
#include<stdlib.h>
/*
* qsort invoke this function
*/
int compare_integers( void const *a, void const *b )
{
register int const *pa = a;
register int const *pb = b;
return *pa > *pb ? 1 : *pa < *pb ? -1 : 0; // cool !
}
int main( int argc, char *argv[] )
{
int *array;
int n_values;
int i;
/*
* see how many values there
*/
printf( "How many values there?\n" );
if( scanf( "%d", &n_values) == EOF || n_values <= 0 ){
printf( "Illegal number of values.\n" );
exit( EXIT_FAILURE );
}
/*
* allocate mem to store the values
*/
array = malloc( n_values * sizeof( int ) );
if( array == NULL ){
printf( "can't get mem for that values!\n" );
exit( EXIT_FAILURE );
}
/*
* load these numbers
*/
for( i = 0; i < n_values; ++i ){
printf("? ");
if( scanf( "%d", array + i ) == EOF ){
printf( "error reading value #%d\n", i );
free( array );
exit( EXIT_FAILURE );
}
}
/*
* sort for these values
*/
qsort( array, n_values, sizeof( int ), compare_integers );
/*
* print the result
*/
printf( "\nThe result is as follows:\n" );
for( i = 0; i < n_values; ++i ){
printf( "%d\n", array[i] );
}
free( array );
exit( 0 );
}
eg:动态复制字符串(方便有效!)
用动态内存分配制作一个字符串的拷贝,调用程序需要检查是否分配成功!
内存的容量应该比字符串的长度多一个byte,用来存储 NUL 字节。
分配失败返回 NULL
#include<stdio.h>
#include<stdlib.h>
char * strdup( char const *string )
{
char *new_string;
new_string = malloc( strlen( string ) * sizeof( char ) + 1 );
if( new_string != NULL )
strcpy( new_string, string );
return new_string;
}