linux 下 C 编程和make的方法 ( 九、malloc 和free的使用 上)

中山野鬼 发布于 2012/04/13 01:13
阅读 1K+
收藏 12
九、malloc ,free的使用
    记得曾经有个等式 为 程序=数据+算法。程序总是在处理数据,这是跑不掉的,哪怕是动下鼠标。不过有些数据的使用空间可以提前约束限制,有些则不行。例如一个函数如果返回值,则这个类型是确定的,它的空间是确定的,(别脑袋转糊涂了,和我说,如果返回个指针,那个空间怎么是确定的?既然你说返回个指针,那么存放这个指针的空间是固定的,我说的是这个)。显然也有些空间是无法确定的。由此,就存在一个动态申请空间的问题。也就是在程序执行中,获取新的空间。通过什么方式?C标准在标准库里,有aligned_alloc ,calloc,malloc,realloc。先不说他们的区别。先说问题。如果你申请了,这空间可不能给别人用。你不停的申请,用银子换来的有限的内存,无论多大,迟早用光。所以有申请,就得有释放。释放就简单了。一个函数。free。
    先说说标准库函数是什么?首先不是C语言本身的东西,是用C语言编写的一些基本函数组成的库。这些“让C语言是高级语言”这句话从一纸标准变成事实。为什么这么说呢?如下的代码是不需要库函数的。
    int main(int argc,char *argv[]){
        return 0;
    }

而如下的代码是需要库函数的
    int main(int argc,char *argv[]){
        printf("hello world\n");
        return 0;
    }

    高级语言之所以高级是因为代码设计可以脱离具体硬件环境,但存在和硬件打交道时如printf,总要有动作,那么C语言的标准就要求,任何平台上的C编译器工具,必须也面向开发者提供C语言标准所要求的,标准函数库。算是基本配置吧。任何硬件平台,要说自己支持C语言开发,都必须提供这些标准库函数的实现方法,并且并入到编译器开发工具 包中。

    但同时,各个编译器为了让你更爽,也白送你很多润滑油(增强的库函数),但是这些库函数一旦换了编译器就麻烦了。所以我尽可能的只说标准库的函数,也建议你,除非不换编译器(那就GCC好了,是支持平台,硬件和OS,最多的了),否则不要用那些润滑油。以方便C语言能更好的移植,毕竟linux崇尚开源嘛。上面说了4个申请空间的函数,和一个释放空间的函数,使用时需要

#include<stdlib.h>

    先谈下realloc,这个要注意,因为标准说它是unspecified的,所以可以通过各种方式实现他,由此导致你的一些代码可能移植性差,如果用他,其实你也没必要用。

    这里先列出函数申明。重复说一下,函数申明的作用是让编译在发现调用这些函数时,可以根据函数的申明检测函数调用是否符合规则,如入口参数,返回参数所传递给的当前变量是否类型和数量上匹配。如果是在一个程序里的,我前面说了。默认函数都是extern,你不

#include <stdlib.h>

,在编译时会有五花八门的警告情况。哈。连接时只要有库能查到对应函数名就OK。不过OK只是链接OK。最终还会有错。我举个例子。

你不include上面的头文件,执行如下代码

char *pc = (char*)malloc(1);

编译器会警告,你尝试将以一个int整型转换成一个地址。在32位下不会错,但在64位下,int整型是32位的,而地址是64位的。如果你打算使用pc的地址,你就等着系统说段出错吧。

为什么会这样,就是因为你不#include,虽然链接时,连接器能准确的找到malloc函数的入口,但由于没有申明,他会默认这个函数的返回值是int。由此编译器做上述报警。有些数据类型不匹配的报警无所谓最多数据错。但地址在传递是,数据宽度发生了变化,麻烦就很大了。

   为了防止代码设计的失误,让编译器检测函数的调用规则是应该的。所以还是得#include 头文件。同时有些头文件里的定义,说不定你还需要用。

    #include <> 和#include "" 的区别就是,前者是可以在默认库头文件的路径内查找。而后者是在当前目录和gcc 命令中 -I的路径中查找。
    void * aligned_alloc(size_t alignment,size_t size);
    void * calloc(size_t nmemb,size_t size);
    void *malloc(size_t size);
    void *realloc(void *ptr,size_t size);
    void free(void*ptr);

    谈上述差异性前,先谈下共性的地方。都是返回void *,void是什么?就是没意义。或者不存在。例如
    void a(void);
    则a函数,没有输入,也没有输出。只做事,不沟通,整一哑巴。那么void *味道就变了。void 可以说是没有意义的,而void *是个指针,一个指针指向没有意义的东西,那是个什么东西?很简单,还是个指针。此时可以理解为,返回了一个指针,但我不确定它是什么类型。也就是说变为成了,我返回了一个指针,该指针指向的类型不确定。如果一个指针为void *,那么这个指针+1后,地址偏差多少?标准规定,一个byte。也就是说sizeof(void) == 1。如果你想不通,就很简单啊,C语言里面和电脑里面最小的寻址单位是byte 啊,就是8个bits,简直最小对齐嘛。
    size_t是个什么玩意?首先是个类型,和long ,int ,short一样,表示类型。size_t类型的变量是用来描述一个类型的位宽的。被描述的类型可以是个独立的类型,例如 int ,float,也可能是一个结构体。既然是描述位宽,所以size_t的类型就是一个无符号的整型。具体多少位,这和int类型具体是多少位一样,是有具体硬件和操作系统决定的。例如我的ubuntu 64下,size_t是8个bytes的宽度。就是64位的。
    好了。说说差异性。

    从标准来看,malloc应该是最基础的。

     aligned_alloc和malloc的区别是,aligned_alloc保证了返回回来的地址,是能对齐的。同时要求size是alignment的整数倍。   

    calloc 相对malloc更接近aligned_alloc,比aligned_alloc多了一个动作,就是全部空间的数据帮你初始化个0.这个耽误时间,当然有时更有用。虽然耽误时间,但你要自己初始化0更耽误时间,当这个初始化必须存在时,用calloc是个好的选择。

     但我通常不用。因为我怕一会calloc,一会malloc,结果需要初始化的用了malloc,不需要的用了calloc。索性都不用。强制自己设计时,在需要的地方要包含初始化代码。

    同样的道理,realloc就更不要用了。realloc的意思是,重新分配,同时将原有空间指定的数据会放到新空间里。要我说我反对使用他的理由有千千万,唯一赞同他的理由是你省事。
    现在说说为什么标题就是malloc和free。因为malloc最基础,最不高级,最不人性,自然用起来最灵活。这里举个例子来说明,如何调用malloc来实现align_alloc或calloc
   
void *my_alloc(size_t alignment,size_t size){
        void *re;
        alignment = (alignment + sizeof(size_t)) & (~(sizeof(size_t) - 1));
        re = malloc(size + alignment);
        if (re == 0) return 0;
        re = re + alignment - (size_t)(re) % alignment);
        *(re - re & sizeof(size_t) - sizeof(size_t)) = bias;
        return re;
    }
    void my_free(void *ptr){
        free(ptr - *((size_t*)ptr - 1));
    }

   (当然,我临时写的,有比这个很高的方式描述)

    上述的做法的目的是我们在传输的指针前,也即,不是给使用者所用到的空间区域保存一个偏移量,这个偏移量用于修正实际malloc申请的空间和用户使用空间首地址的差异值。例如假设 alignment = 128, 7 * 8 bytes。而malloc申请的空间地址为0x10,则我们必须给使用者0x80这个地址,由此,前面多出来的0x70这个数值需要保存在0x10到0x80之间某个my_free可以识别到的地方,(这是malloc申请到的有效空间),由此存储的位置紧邻my_alloc返回的值,这样就和my_free达成默契。同时为了有效存放,我们需要返回给客户的地址,不仅和客户要求的aligment对齐,还要和sizeof(size_t)对齐。因此对alignment需要进行修正。
    而对于calloc的模拟则简单很多了。
  
void *my_calloc(size_t nmemb,size_t size){
        void *re = my_alloc(nmemb,size);
        void *p =re;
        if (re == 0) return 0;
        while (size > 0){
            *p++ = 0; size--;
        }
    }

    对于realloc的模拟则为
   
void *my_realloc(void *ptr,size_t size){
        void *pn = my_alloc(sizeof(size_t),size);//这里没有考虑ptr对齐的问题。
        size_t i;
        if (pn == 0) return ptr;
        if (ptr == 0) return pn;
        for (i = 0 ; i < size ; i++){
            *pn++ = *ptr++;
        }
        my_free(ptr);
        return pn;
    }

( 我写这写代码不是为了用的,只是为了让大家更好的理解和区别几个函数的不同作用)


    由此,我们不再谈其他的动作。只谈malloc和free。如果没听过数据存储在堆或栈中,恭喜你,记住堆栈是个数据结构就行了,堆和栈是针对在代码不同地方的数据其空间所在的位置,malloc都是从堆里来的。这里谈下malloc的空间究竟哪来的。我们需要关注几点内容,
    1、malloc申请来的地址,都是连续的。(虚拟地址)
    2、malloc申请的空间由于是任意大小的,但不同的起始位置的空间,对读写的速度有很大的影响。
    3、在有OS的情况下,通常一块空间大小如果正好等于L1 CACHE的一个PAGE,则相对其他方案更为高效。通常一个PAGE为4Kbytes。
    4、OS在大多数有硬件内存管理控制器的CPU上,可以将很多实际物理不连续的PAGE,对应到连续的地址空间中。
    简单的解释一下。
    关于1,意思是让你放心,你申请的malloc的指针,p。只要在 size范围内,都是连续有效的,不会存在中间某个单元不能用的情况。
    关于2,3,则为每个malloc申请的空间都是独立的一个page 则很浪费,如果每次malloc实际只要了1个byte 呢?
    关于4,不要担心实际存储空间不在一个连续的地址范围。只要针对page,硬件会自动帮你映射好你的地址空间和具体存储位置。
    由此,解释下malloc的空间从哪来的?是从OS所管理的物理空间里借来的。首先谈,借,借了就要还,所以记得要free。其次谈物理空间。这其实是屁话,没有内存条,你存哪?但是物理空间是被OS管理的。OS的价值在于有效的利用好这些物理空间。因此首先对物理空间按照page切成块。同时如果比page小malloc,你甚至可能和别人共用一个page,这得看OS怎么决策。而如果你的空间大于page,OS保持你的存储空间尽可能的在物理空间上连续,但不绝对,除非特别要求,同时肯定是PAGE的整数倍。
    废话这些,是希望注意,linux的page是4KBYTE。以此为基数,在使用malloc要尽可能按照如下约束操作:
    1、malloc的size 是4Kbytes的倍数。如果你各处的malloc加起来也没有4Kbytes。那么你自己申请一个4KBYTES,然后自己规划每处的使用。而free则一次就OK。因为这样有可能比你多次malloc申请空间更省系统的内存。所以记得,出于高效利用资源的角度,不浪费系统内存的目的,你要4Kbytes的申请空间。
    2、如果除了主函数外,一个函数里,malloc了个空间,同时又free了这个空间,则你应当认为这是个设计失误。你完全可以将这些工作放到外部函数里操作。依次向外推,直到属于主函数调用的两个不同函数实现,一个是alloc_all,一个是free_all。更别提for循环内出现malloc free。
    3、除非你在做个足够大的系统。同时至少有自己的一套内部存储空间逻辑,否则尽可能的让你的所有计算都落于不大于CPU最大CACHE 2倍的空间。也就是说无论你申请多大的空间。真正给与计算操作的窗口不要大于CACHE2倍。
    4、malloc的申请次数越少越好。即便你没有自己的内存管理模块,也要有组织数据空间的代码。
    5、由于page的存在。计算的数据之间的距离尽量小。最好始终保持小于page的尺寸。也即任意两个二元操作的数据源的地址偏差尽量小于4KBytes。

    好了。说到malloc和free本身,我们就这么多。其他更多的事情是OS帮你完成的。你大可以安心开始通过自己申请的空间进行数据计算了。不过空间的内容的访问,显然都是指针操作了。有人说,C语言指针是最难的。我的理解,是最容易出错的,但不是最难的。我现在的代码也经常因为指针错误而结果错误,查清问题时,发现都是弱智错误,或者键盘错误。我又不是老师,也不是挣稿费,所以如果形而上学的如学校那样就告诉你malloc,和free怎么调用,实在太无聊。下面就说点实用的。对指针的检测。
    我说了。我们要正向设计,不要通过debug来反向查错。不过指针问题往往是动态的,什么是动态的呢。就是跑起来才有机会出错。不跑起来,代码没有显而易见的漏洞。因此,对于指针的测试,我们虽然不通过debug,但也要借助检测系统来完成检测。
    这里要扩展一下,这里的检测,包括信息打印,和LOG系统是不一样的。LOG的对象是正确的系统。检测的基本单元是模块。而这里的检测,我们也通称debug系统吧。基本单元是动作。是属于对模块内部的各个配件的检测。
    不过由于是动态的问题,则检测应当尽可能保证测试不影响系统本身的运行状态。大家都知道VC,有DEBUG模式,可以很方便的F9,F5,同时通过WATCH和MEM窗口观测运行状态,但有个问题,release模式下,这样的debug就犯晕了,同时很多动态错误更多的在release模式下而不是debug模式出现。由此我们确定一个MEM_debug的模块目标如下:
    1、可通过宏的方式,打开或关闭检测,当关闭时检测代码全部在编译时被拿掉。
    2、检测的打开,尽可能的少的影响代码本身的运行效率。检测代码的是否工作和编译效率选项无关。
    3、可以进行以下几种检测。
    a、是否所有malloc都free了,且malloc和free的地址是一样的。
    b、是否一个指针超出了指定的寻址范围。例如一个指针希望是对刚申请的从 0x10到0x70的空间的内容进行操作。而他却正在读取或写出到0x80这个空间内。
    c、可以根据条件的检测,当条件未达到前,不做检测。
    d、可在执行态时,根据判断,程序暂停执行,打印一定的信息到屏幕上。
    同样,我们还是用create_module.sh 。不过这个脚本我已经增加了include ,lib两个PATH。
    $./create_module.sh malloc_free

    这里需要说明一下,要想将检测代码,通过宏的方式打开或关闭,需要可这个模块至少是头文件需要持久的保留在代码里。想了半天觉得还是malloc_free这个名字更合适更合适。
    构建模块完成后,进行测试。测试没有问题后,进行如下代码填写。 malloc_free.h,malloc_free.c ,test_malloc_free_main.c
#ifndef _malloc_free_H_
#define _malloc_free_H_
#include <stdlib.h>
#define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
#define MAX_MALLOC_NUM  65535 // max malloc times ,you can change
#define __MMDB_FLAG__
#ifndef __MMDB_FLAG__
//c_malloc c_free means malloc by check,not calloc,not type cmalloc!!!!!
#define c_malloc(a) malloc(a)
#define c_free(a) free(a)

#else

//ins_inc_file

//ins_typedef_def

//ins_def

//ins_func_declare
void malloc_free_init(void*);

void *c_malloc(size_t size);
void c_free(void *ptr);

#endif
#endif //_malloc_free_H_


 /***************
src/malloc_free.c
 by luckystar
 ***************/
#include "malloc_free.h"
#include <stdio.h>

enum {
    ERR_NULL,
    ERR_FREE_MORE,
    ERR_FREE_LACK,
    ERR_ALLOC_OVERFLOW,
    ERR_MAX_NUM
};
const static char error_str[ERR_MAX_NUM][256] = {"malloc_free is ok!\n","too much free func called!\n",\
"free is lack!\n","too much alloc func called!\n"};
#define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
#define DO_ERR_EX(n,E) do {ERROR(E);n = 0;exit(1);} while (0)
#define CHECK_ALLOC(n) do {if ((n)>=MAX_MALLOC_NUM){DO_ERR_EX(n,ERR_ALLOC_OVERFLOW);}}while(0)
#define CHECK_FREE(n) do {if ((n) == 0){DO_ERR_EX(n,ERR_FREE_MORE);}}while(0)
#define CHECK_FREE_LACK(n) do {if ((n)){ERROR(ERR_FREE_LACK);}}while(0)
static int malloc_free_flag =0;
static long memory_count = 0;
static unsigned int error_type = 0;
void malloc_free_destory(void);
void malloc_free_init(void*pv){
    if (malloc_free_flag) {
        //log("module inited..",X);
        return;
    }
    malloc_free_flag = 1;
    //todo:module init...
    error_type = 0;
    atexit(malloc_free_destory);
}
void *c_malloc(size_t size){
    void *re;
    CHECK_ALLOC(memory_count);
    re = malloc(size);
    memory_count += (re != 0);
    return re;
}
void c_free(void *ptr){
    if (ptr){
        CHECK_FREE(memory_count);
        free(ptr);
        memory_count--;
    }
    return;
}
void malloc_free_destory(void){
    if (!malloc_free_flag) {
        //log("module not inited..",X);
        return;
    }
    malloc_free_flag = 0;
    //todo:module destory...
    if (error_type == 0){
        CHECK_FREE_LACK(memory_count);
    }
    fprintf(stderr,"%s",error_str[error_type]);

}

 #include <stdlib.h>


#include "malloc_free.h"
#include <stdio.h>
#include <stdlib.h>

typedef void (* TEST_FUNC)(void);

static void test0(void){
    void *p1 = 0;
    char *p2 = 0;
    printf("test0\n");
    p1 =(char*)c_malloc(4);//*4);
    p2 = (char*)c_malloc(6);
    c_free(p1);
    c_free(p2);
    return; //normal check
}
static void test1(void){
    char *p1,*p2;
    p1 = (char*)c_malloc(5);
    p2 = (char*)c_malloc(6);
    c_free(p1);
    //cfree(p2);
    return; //free lack check
}
static void test2(void){
    char *p1,*p2,*p3;
    p1 = (char*)c_malloc(5);
    p3 = p2 = (char*)c_malloc(6);
    c_free(p1);
    c_free(p2);
    c_free(p3);
    return; //free more check
}
static void test3(void){
    char **pp = (char**)c_malloc(sizeof(char*)*MAX_MALLOC_NUM + 1);
    int i;
    for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){
        pp[i] = (char*)c_malloc(2);
    }
    return; //alloc more check
}
#define TEST_MASK 3
TEST_FUNC test_a[TEST_MASK+1]= {test0,test1,test2,test3};
  
int main(int argc,char *argv[]){
    int mode;
    printf("hello test_malloc_free_main now run...\n");
    malloc_free_init(0);
    if (argc < 2){
        printf("need parameters!\n");
        return 1;
    }
    mode = argv[1][0] - '0';
    test_a[mode & TEST_MASK]();
    malloc_free_destory();
    printf("hello test_malloc_free_main now exit...\n");
    return 0;
}

    编译,运行。记得加上参数, 0到4,一共4种例如
    $bin/test_malloc_free_main 2 ,#表示进行第三种情况判断。
    0 .. 正常情况,测试malloc free是成对的。
    1 .. malloc次数多余free。
    2 .. free 次数多余malloc。
     3 .. malloc的次数多余规定限制,目前定在65535。
    现在说下代码。重复编程思想,保持小步多测试的原则。首先实现对malloc free是否成对的检测代码设计。
    看下test_malloc_free_main的代码
    typedef void (* TEST_FUNC)(void);
    这是申明一个函数指针类型。而类型名称就是TEST_FUNC。这里不强调上述语法问题。其他书上都有,标准里也有。谈下函数指针是个什么玩意。先说一下下面的代码
    #define TEST_MASK 3
    TEST_FUNC test_a[TEST_MASK+1]= {test0,test1,test2,test3};

    这是函数指针的数组,不是函数数组(其实是一回事,但要明确是函数指针的数组)。TEST_MASK的define另说,也好像说过了。这里不扯。你可以看到,对这个数组的赋值是一个个函数。

    函数大家都知道是代码。貌似代码和数据是两个世界的东西。但对于数字计算机而言。代码还是数据。而函数名是用于指向一段代码的地址。当你调用某个函数时,实际计算机是在跳转到对应该函数的第一个执行语句的位置。那么这个跳转的内容,实际就是个指针。因此可以这么理解。

    函数在实际运行时,有自己的空间。而函数名则是指向这个空间的地址的指针。此时test0...test3不是表示函数本身的意思,而是函数名。如下

    printf("hello world!\n");

    此时printf,也是个函数名,指向了printf的函数实际在内存中的位置。现在我再解释为什么TEST_FUNC是函数指针类型就有点多余了。

    这种操作方式有什么好处呢?如下操作也可
    if (mode == 0) {test0();}else
    if (mode == 1) {test1();}else
    if (mode == 2) {test2();}else
    if (mode == 3) {test3();}

但进入函数前,等概率的需要判断2次。你说无所谓。那么20个函数选择的进入呢?同时扩充模式,需要额外补充上述代码。此时用函数指针数组的方式就再好不过了。而如果只是个函数指针的变量,例如这样定义

 
   TEST_FUNC tf = test0;
    则可以直接使用
    tf();

    此时就可以进入实际test0函数了。

    下面说下malloc_free.h

   
#define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
    #define MAX_MALLOC_NUM  65535 // max malloc times ,you can change
    #define __MMDB_FLAG__
    ALLOC_PAGE_SIZE是强迫malloc按照4Kbyte倍数进行分配空间。不过这是废话,因为OS确实也是如此布局,如果你的申请的空间大于4K。而此处也强制使用者小于4k时,按照4K进行分配,以让使用者评估下,是否可以压缩malloc的次数,合并一些空间。现在暂时还没用它。
    MAX_MALLOC_NUM,这个,抬杠的说,等于1就够了。余下自己做内容管理。暂且 65535吧。如果比这个还大,你真要考虑调整malloc free的策略了。OS和硬件系统的资源也是有限的。
    __MMDB_FULAG__是用于选择使用本模块还是直接使用malloc 或free。我个人建议始终打开,因为但凡要使用malloc,应该是系统处于初始化状态。而不是运行态,此时代码的性能并不重要。安全更要紧。

    说下malloc_free.c。
    首先这里有很多非变量申明或函数申明的东西。通常可以直接放在头文件里。如 enum 和define。放在这里的目的是保证不被篡改。对定义等应当尊崇一个原则,如果不是给别的C文件使用的,不应当存放在头文件里处理。哪怕是类型定义。
    说下memory_count,是用于计数的。成功的malloc 则+1,成功的free则 -1,所以需要注意
   
CHECK_ALLOC(memory_count);
    re = malloc(size);
    memory_count += (re != 0);

    if (ptr){
        CHECK_FREE(memory_count);
        free(ptr);
        memory_count--;
    }

    两段代中memory_count出现的顺序。
    下面说下CHECK_XXX的定义怎么设计。首先我们的目标存在3种检测。
    memory_count >= MAX_MALLOC_NUM 申请前
    memory_count == 0 , 释放前
    memory_count != 0 , 退出前
    由于动作不同,比较方式不同,因此无法简单合并,则需要3个define 。其实还是可以合并的。我们可以上述2修改如下
    memory_count <= 0 , 释放前
    此时这两个判断引发的动作相同,因此
#define CHECK_FREE(n) do {if ((n) == 0){DO_ERR_EX(n,ERR_FREE_MORE);}}while(0)
    完全可以修改如下
#define CHECK_FREE(n) do {if (0 >= (n)){DO_ERR_EX(n,ERR_FREE_MORE);}}while(0)
    此时结合
#define CHECK_ALLOC(n) do {if ((n)>=MAX_MALLOC_NUM){DO_ERR_EX(n,ERR_ALLOC_OVERFLOW);}}while(0)
    可以得到如下#define 方式
    #define CHECK_MODE(l,r,n,ERR_M) do {if ((l) >= (r)){DO_ERR_EX(n,ERR_M);}}while(0)
    #define CHECK_FREE(n) CHECK_MODE(0,n,n,ERR_FREE_MORE)
    #define CHECK_ALLOC(n) CHECK_MODE(n,MAX_MALLOC_NUM,n,ERR_ALLOC_OVERFLOW)

    或者两处分别引用CHECK_FREE,CHECK_ALLOC的地方换成统一的CHECK_MODE。但这样并不是使用define的好习惯。

    如何规划define ,我个人认为应当尊崇过程化和引用简化的两个目标的折中。

     所谓过程化,就是当你发现你有两处代码,过程有相同处则应该合并,使用define 方式进行处理,而引用简化,包含了define 后的参数多少,参数意义是否明确,define本身是否表达明确含义三个评判内容。

     对于“引用简化”,在该define不是最终代码引用时,可以降低要求。因为同样的过程,参数复杂,但由于是define过程化了,一个引用对了,则其他,至少该步骤都对了。

    为什么有如下定义
    #define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
    #define DO_ERR_EX(n,E) do {ERROR(E);n = 0;exit(1);} while (0)
    这是从过程化细分的角度考虑的。此处完全可以做到
//    #define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
    #define DO_ERR_EX(n,E) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);n = 0;exit(1);} while (0)
    但实际上ERROR(n)有明确的含义,也就是说有明确的独立存在价值,也即,修正n后对error_type 进行赋值。而在
    #define CHECK_FREE_LACK(n) do {if ((n)){ERROR(ERR_FREE_LACK);}}while(0)
    也确实被使用到了。
    对于新手不知如何较好规划define,那么可以用个简单的方法。先用函数套函数的方式,将目标切割成小函数依次实现。确认没有错了,在进行 #define 。但这个只是个方法。不是精髓,精髓是你能理解一个代码片中,都有哪些步骤。如果有些步骤具备独立可描述的含义,则此时就可以作为子函数或#define的目标。而该步骤的含义越独立,则define 被引用的机会越多,该 define 的价值越大。这种价值越大表现在两个方面。其一是减少了同样明确目标的代码书写错误。其二当该目标需要调整时,简化了修改和测试的工作量。
    有些书籍的论点,说#define是C语言的历史造成的,使用define不是个良好的代码设计习惯。哈。我只能说,这种名言绝对可以类比“ C++的类是为了体现差异C而多出来的,使用C++应该尽量避免使用类这种不良好的习惯。”这种极品言论。
    C语言的特点就是面向硬件,面向模块,面向过程。而过程,包括代码片,此时 #define 的价值就体现在 过程化的一致性上。认为define 会带来问题,只能说没有学会使用define 的正确方法而已。屠夫总不至于刀可以杀人而放弃工具吧。
    另一个介绍。atexit,这个函数的参数是一个void (*)(void);的函数指针类型。目的是在exit执行时,根据先进后出的顺序,依次调用参数所给与的函数指针所指向的函数。而在
    malloc_free_init中我使用了,
        atexit(malloc_free_destory);
    则在test_malloc_free_main.c中就不需要在调用了。因为main 函数在return 0后,会自动调用他。为什么需要这样做,因为由于检测存在,导致整个程序不一定在main中退出,因此,需要强制在退出前执行malloc_free_destory。
    为什么我上述的DO_ERR_EX 中在exit(1);前都要让error_type = 0;这是以为在malloc_free_destory 中存在一个正常退出对memory_count == 0的判断。已确定在 CHECK_ALLOC和CHECK_FREE不出错下,检测malloc free成对的问题。
    OK。先到此,针对目标的其他动作后续处理。
加载中
0
breezing
breezing
看来野鬼是夜猫子。。。
中山野鬼
中山野鬼
哈,是的哎,下午3点才起。
0
xinzaibing
xinzaibing
对于malloc,还是比较常用calloc,能自动初始化新申请的内存空间
0
0
defu
defu
野鬼老师,注意身体啊,老是这么熬夜,吃不消啊。
返回顶部
顶部