C和C指针小记(十六)-动态内存分配
动态内存分配
1.1 为什么使用动态内存分配
直接声明数组的方式的缺点:
- 声明数组必须指定长度限制.无法处理超过声明长度的数组.
- 如果声明更大的常量来弥补第一个缺点,会造成更多的内存浪费.
 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 );
C和C指针小记(十六)-动态内存分配的更多相关文章
- C和指针 第十一章 动态内存分配
		声明数组时,必须指定数组长度,才可以编译,但是如果需要在运行时,指定数组的长度的话,那么就需要动态的分配内存. C函数库stdlib.h提供了两个函数,malloc和free,分别用于执行动态内存分配 ... 
- C++学习笔记(十一):void*指针、类型转换和动态内存分配
		void*指针 void关键字表示“空类型”的概念.但是,这里的“空类型”不表示“任意类型”,而是表示不存在的意思,也就是说C/C++不允许你写语句void a,不存在类型为void的东西. void ... 
- <c和指针>学习笔记5动态内存分配和预处理器
		1 动态内存 比如声明数组得时候,我们需要提前预估数组长度,分配大了浪费,少了就更不好操作了.从而引入动态分配,需要的时候再分配. (1)malloc和free void *malloc(size_t ... 
- C++指针和动态内存分配
		指针和动态内存分配 数组与指针 数组 数组名是一个指针常量. 数组名传递数据时,传递的是地址. 数组作为函数参数时不指定第一维大小. 对象数组 A a[2] = {A(1,2)}; 执行时先调用有参数 ... 
- 《C和指针》 读书笔记 -- 第11章 动态内存分配
		1.C函数库提供了两个函数,malloc和free,分别用于执行动态内存分配和释放,这些函数维护一个可用内存池. void *malloc(size_t size);//返回指向分配的内存块起始位置的 ... 
- C动态内存分配(C与指针实例)
		主要初步介绍malloc.free.calloc.realloc的基本.日后会有更详细的内容. malloc.free分别用于动态内存分配和释放. malloc会从内存池里提取一块合适的内存(连续的) ... 
- 数据结构基础——指针及动态内存分配(malloc)
		一.指针 C语言中的指针是一种数据类型,比如说我们用int *a;就定义了一个指针a,它指向一个int类型的数.但是这个指针是未初始化的,所以,一般的,我们都在创建指针时初始化它,以免出错,在还不吃的 ... 
- 《C++ Primer Plus》读书笔记之十—类和动态内存分配
		第12章 类和动态内存分配 1.不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存.可以在类声明之外使用单独的语句进行初始化,这是因为静态类成员是单独存储的,而不是对象的 ... 
- c++  动态数组,指针与动态内存分配
		教学内容: 内存的使用 动态内存分配malloc函数 分配内存时使用sizeof运算符 用指针访问内存 以数组的形式访问内存 一.内存的使用 堆(heap) 在程序执行期间分配内存时,内存区域中的这个 ... 
随机推荐
- 用virsh console vhosts 卡住
			[root@666 ok]# virsh list --all Id Name State ---------------------------------------------------- 1 ... 
- idea 使用正则表达式 进行匹配替换
			关于正则表达式 可以参考相应的笔记 另外 如果要提取正则表达式中匹配到的内容,使用$1 - $... 按顺序取(第一个表达式 到 第N个表达式匹配到的数据), 这点和linux正则获取的方式是一样的 
- 60cms Cookies欺骗漏洞审计
			源码地址:https://files.cnblogs.com/files/ssooking/60cms.zip 运行60cms目录下的Netbox.exe即可开启Asp Web服务,默认端口80 环境 ... 
- Linux解压缩命令tar
			tar -c: 建立压缩档案-x:解压-t:查看内容-r:向压缩归档文件末尾追加文件-u:更新原压缩包中的文件 这五个是独立的命令,压缩解压都要用到其中一个,可以和别的命令连用但只能用其中一个.下面的 ... 
- Http url  MVC Request Query Form 传参专贴
			一.工具区 [参考]postman中 form-data.x-www-form-urlencoded.raw.binary的区别--转 二..net MVC 三..net WebForm 四.Java ... 
- javassist实例
			我们常用到的动态特性主要是反射,在运行时查找对象属性.方法,修改作用域,通过方法名称调用方法等.在线的应用不会频繁使用反射,因为反射的性能开销较大.其实还有一种和反射一样强大的特性,但是开销却很低,它 ... 
- ambari 安装HDP3.0.1后,启动服务的问题记录
			HDP的ambari集成安装工具真的是比ClouderaManager差上那么一点儿,不说安装的时候就麻烦,即使软件安装包已成功安装,也不意味着可以正常使用了,启动HDP集群过程中还会有不少的错误! ... 
- Flask-SQLAlchemy 中多表链接查询(不使用外键)
			SQLAlchemy 是一个功能强大的 ORM . Flask-SQLAlchemy 是一个 Flask 插件,它让我们在 Flask 框架中使用 SQLAlchemy 变得更容易. 本篇介绍我在使用 ... 
- fiddler 使用记录
			fiddler 工作原理 Fiddler 启动后将自己变成一个代理服务器,这个代理服务器默认监听 127.0.0.1:8888. Filddler 启动后浏览器的代理会被自动更改为 127.0.0.1 ... 
- Linux下常见命令
			=============挂载和登陆命令======================================== Mount:挂载命令. 比方挂载光驱mount /dev/cdrom /mnt ... 
