C和C指针小记(十六)-动态内存分配

动态内存分配

1.1 为什么使用动态内存分配

直接声明数组的方式的缺点:
1) 声明数组必须指定长度限制.无法处理超过声明长度的数组.
2) 如果声明更大的常量来弥补第一个缺点,会造成更多的内存浪费.
3)如果输入数组的数据超过来数组的容纳范围,程序必须以一种合理的方式作出响应.但是程序员一般不会做这个判断.

1.2 malloc 和 free

malloc 和 free 分别用于执行动态分配内存和释放.
stdlib.h 中声明来这两个函数的原型
void malloc( size_t size );
void free(void
point );
malloc 分配的是一块连续的内存,不会分开位于两块或多块不同的内存.不同的编译器可能会分配给你要求的或要求多一点的内存.比如你malloc(100),编译器可能分配100 或 105.
如果内存池空了,malloc 函数会向操作系统请求,要求得到更多的内存,并在这块内存上执行分配任务,如果操作系统无法向malloc提供更多的内存,会返回NULL指针.
所以要对每个从malloc返回的指针都进行检测,确保它非NULL.
free 的参数要么是NULL,要么是一个先前从malloc,calloc 或realloc返回的值.

11.3 calloc 和 realloc

void *calloc( size_t num_elememts, size_t element_size);
void realloc( void *ptr, size_t new_size );

calloc 也用于分配内存. calloc 相比 malloc 会自返回内存指针之前把它初始化为0. 参数需要包括所需元素的数量和每个元素的字节数.
realloc 用于修改原先已经分配的内存块的大小. 你可以扩大或者缩小使用的内存块.
如果原先的内存块无法改变大小,realloc 将分配另一块正确大小的内存卡,并把原先那块内存的内容复制到新块上.
使用realloc之后,就不能使用指向旧内存的指针,而要改用 realloc 所返回的新指针.
如果realloc 的第一个参数是 NULL,那么它的行为就和 malloc 一样.

11.4 使用动态内存分配

//分配100个字节的内存
int *pi;
pi = malloc(100)
if( pi == NULL ) {
    printf("Out of memory!\n");
    exit(1);
}

这样就成功分配来100个字节,可以用来存储 25个 int,不过我们有更好的分配内存的写法,让代码的字面意义和我们想要存书的内容类型相匹配.

pi = malloc(25 * sizeof(int) );

使用内存

int *pi2, i;
pi2 = pi;
for( i = 0; i < 25; i += 1){
    *pi2++ = 0;
}
//或者
int i;
for (i = 0; i < 25; i += 1 )
    pi[i] = 0;

1.5 常见的内存错误

对NULL指针进行解引用
对分配的内存进行操作时越过边界
释放并非动态分配的内存
试图释放一块分配内存的一部分
试图使用被释放之后的内存块

针对忘记检查内存是否分配成功的解决方法

//alloc.h
//定义一个不易发生错误的内存分配器
#include <stdlib.h>
#define malloc    不要直接调用malloc!
#define MALLOC(num ,type) (type *)alloc( (num) * sizeof(type) )
extern void *alloc( size_t size );
//alloc.c
//不易发生错误的内存分配器的实现
#include <stdio.h>
#include "alloc.h"
#undef malloc
    void * alloc( size_t size) {
        //请求所需的内存,并检查确实分配成功
        new_mem = malloc( size );
        if( new_mem == NULL ){
            printf("Out of memory!\n";
            exit(1);
        }
        return new_mem;
    }
//a_client.c
//使用
#include "alloc.c";
void function() {
    int *new_memory;
    //获得一串整型数的空间
    new_memory = MALLOC( 25, int );
}

释放一块内存的一部分是不允许的

pi = malloc( 10 *sizeof( int ) );
free( pi + 5);//不被允许

不要访问已经被free函数释放了的内存.
很常见的情况,你对一个指向动态内存的指针进行了复制,而且这个指针的几份拷贝散布于程序各处,你无法保证当你使用一个指针时它所指向的内存是不是已被另一个指针释放.所以,必须保证程序中所有使用这块内存的地方在这块内存被释放之前停止对它的使用.

内存泄漏
动态分配的内存不使用时因该被释放,这样这块内存以后才可以被重新分配使用.分配的内存使用完毕之后不释放将引起内存泄漏.

1.6 内存分配的实例

内存分配一个常见的用途就是为那些长度在运行时才知道的数组分配内存空间.

//读取、排序和打印一列整数
#include <stdlib.h>
#include <stdio.h>

//该函数由qsort调用,用于比较整型值
int compare_integers( void *a, void const *b ){
    register int const *pa = a;
    register int const *pb = b;
    return *pa > *pb ? 1 : *pa < *pb ? -1 : 0;
}

int main(){
    int *array;
    int n_values;
    int i;
    //观察共有多少个值
    printf("How many values are there?");
    if( scanf("%d", &n_values ) != 1 || n_value <= 0) {
        printf("Illegal number of values.\n");
        exit( EXIT_FAILURE );
    }
    //分配内存用于存储这些值
    array = malloc(n_values * sizeof( int ) );
    if(array == NULL ){
        printf("Can't get memory for that many values.\n");
        eixt( EXIT_FAILURE );
    }
    //读取这些值
    for( i = 0; i < n_values; i += 1 ) {
        printf("? ");
        if(scanf("%d", array + i ) != 1 ){
            printf("Error reading value #%d\n, i );
            free( array );
            exit(EXIT_FAILURE);
        }    
    }
    //对这些值排序
    qsort( array, n_values, sizeof( int ), compare_integers );
    //打印这些值
    for(i = 0; i < n_values;i += 1)
        printf( "%d\n", array[i] );
    //释放内存并退出
    free( array );
    return EXIT_SUCCESS;
}
//用动态分配内存制作一个字符串的一份拷贝. 注意:调用程序应该负责检查这块内存是否分配成功,这样作允许调用程序以任何它所希望的方式对错误作出反应
#include <stdlib.h>
#include <string.h>
char strdup( char const *string ){
    char *new_string;
    //请求足够长的内存,用于存储字符串和它的结尾NULL字节
    new_string = malloc( strlen( string ) + 1 );
    //如我我们得到内存,就复制字符串
    if( new_string != NULL )
        strcpy(new_string, string );
    return new_string;
}

这个函数非常方便,也非常有用.尽管标准没有提及,但许多环境都把它作为函数库的一部分.

存货系统的声明:

//inventor.c
//存货记录的声明
//包含零件专用信息的结构
typedef struct {
    int cost;
    int supplier;
    //其他信息...
} Partinfo;

//存储装配件专用信息的结构
typedef struct {
    int n_parts;
    struct SUBASSYPART {
        char partno[10];
        short quan;
    } *part;
} Subassyinfo;

//存货记录结构,它是一个变体记录
typedef struct {
    char partno[10];
    int quan;
    enum {PART, SUBASSY } type;
    union {
        Partinfo *part;
        Subassyinfo *subassy;
    } info;
} Invrec;

动态创建变体记录

//invertor.c
//用于创建 SUBASSEMBLY( 装配件) 存货记录的函数
#include <stdlib.h>
#include <stdio.h>
#include "invertor.h"
Invrec *create_subassy_record( int n_parts){
    Inverc *new_rec;
    //试图为Invrec 部分分配内存
    new_rec = malloc( sizeof ( Invrec ) );
    if( new_rec != NULL) {
        //内存分配成功,现在存储SUBASSYINFO 部分
        new_rec->info.subassy = malloc( sizeof( Subassinfo ) );
        if (new_rec->info.subassy != NULL){
            //为零件获取一个足够大的数组
            new_rec->info.subassy->part = malloc( n_parts * sizeof( struct SUBASSYPART ) );
            if( new_rec->info.subassy->part != NULL ){
                //获取内存,天聪我们知道的字段,然后返回
                new_rec->type = SUBASSY;
                new_rec->info.subassy->n_parts = n_parts;
                return new_rec;
            }
        //内存已用完,释放我们原先分配的内存
            free( new_rec -> info.subassy );
        }    
        free( new_rec );
    }
    return NULL;
}

变体记录的销毁

//释放存货记录的函数
#include <stdlib.h>
#include "inventor.h"
void discard_inventory_record( Invrec *record ) {
    //删除记录中的变体部分
    switch(record->type){
        case SUBASSY:
            free( record->info.subassy->part );
            free( record->info.subassy );
            break;
        case PART:
            free( record->info.part );
            break;    
    }
    //    删除记录的主题部分
    free( record );
}

更高效的释放记录的方法,不区分变体部分的零件和装配部件. 因为free 函数不区分指向联合指针内部成员的类型. 换句话说,free接受一个合法指针就会正确释放内存

if(record->type == SUBASSY )
    free( record->info.subassy->part );
free( record->info.part );
free( record );

猜你喜欢

转载自www.cnblogs.com/wjw-blog/p/10449214.html