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

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

1 int main(int argc,char *argv[]){
2     return 0;
3 }

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

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

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

1 #include<stdlib.h>

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

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

1 #include <stdlib.h>

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

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

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

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

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

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

    #include <> 和#include "" 的区别就是,前者是可以在默认库头文件的路径内查找。而后者是在当前目录和gcc 命令中 -I的路径中查找。 
1 void * aligned_alloc(size_t alignment,size_t size);
2 void calloc(size_t nmemb,size_t size);
3 void *malloc(size_t size);
4 void *realloc(void *ptr,size_t size);
5 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 
    
01 void *my_alloc(size_t alignment,size_t size){
02         void *re;
03         alignment = (alignment + sizeof(size_t)) & (~(sizeof(size_t) - 1));
04         re = malloc(size + alignment);
05         if (re == 0) return 0;
06         re = re + alignment - (size_t)(re) % alignment);
07         *(re - re & sizeof(size_t) - sizeof(size_t)) = bias;
08         return re;
09     }
10     void my_free(void *ptr){
11         free(ptr - *((size_t*)ptr - 1));
12     }

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

    上述的做法的目的是我们在传输的指针前,也即,不是给使用者所用到的空间区域保存一个偏移量,这个偏移量用于修正实际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的模拟则简单很多了。 
   
1 void *my_calloc(size_t nmemb,size_t size){
2         void *re = my_alloc(nmemb,size);
3         void *p =re;
4         if (re == 0) return 0;
5         while (size > 0){
6             *p++ = 0; size--;
7         }
8     }

    对于realloc的模拟则为 
    
01 void *my_realloc(void *ptr,size_t size){
02         void *pn = my_alloc(sizeof(size_t),size);//这里没有考虑ptr对齐的问题。
03         size_t i;
04         if (pn == 0) return ptr;
05         if (ptr == 0) return pn;
06         for (i = 0 ; i < size ; i++){
07             *pn++ = *ptr++;
08         }
09         my_free(ptr);
10         return pn;
11     }

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


    由此,我们不再谈其他的动作。只谈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。 
1 $./create_module.sh malloc_free

    这里需要说明一下,要想将检测代码,通过宏的方式打开或关闭,需要可这个模块至少是头文件需要持久的保留在代码里。想了半天觉得还是malloc_free这个名字更合适更合适。 
    构建模块完成后,进行测试。测试没有问题后,进行如下代码填写。 malloc_free.h,malloc_free.c ,test_malloc_free_main.c 
01 #ifndef _malloc_free_H_
02 #define _malloc_free_H_
03 #include <stdlib.h>
04 #define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
05 #define MAX_MALLOC_NUM  65535 // max malloc times ,you can change
06 #define __MMDB_FLAG__
07 #ifndef __MMDB_FLAG__
08 //c_malloc c_free means malloc by check,not calloc,not type cmalloc!!!!!
09 #define c_malloc(a) malloc(a)
10 #define c_free(a) free(a)
11  
12 #else
13  
14 //ins_inc_file
15  
16 //ins_typedef_def
17  
18 //ins_def
19  
20 //ins_func_declare
21 void malloc_free_init(void*);
22  
23 void *c_malloc(size_t size);
24 void c_free(void *ptr);
25  
26 #endif
27 #endif //_malloc_free_H_


01 /***************
02 src/malloc_free.c
03  by luckystar
04  ***************/
05 #include "malloc_free.h"
06 #include <stdio.h>
07  
08 enum {
09     ERR_NULL,
10     ERR_FREE_MORE,
11     ERR_FREE_LACK,
12     ERR_ALLOC_OVERFLOW,
13     ERR_MAX_NUM
14 };
15 const static charerror_str[ERR_MAX_NUM][256] = {"malloc_free is ok!\n","too much free func called!\n",\
16 "free is lack!\n","too much alloc func called!\n"};
17 #define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
18 #define DO_ERR_EX(n,E) do {ERROR(E);n = 0;exit(1);} while (0)
19 #define CHECK_ALLOC(n) do {if ((n)>=MAX_MALLOC_NUM){DO_ERR_EX(n,ERR_ALLOC_OVERFLOW);}}while(0)
20 #define CHECK_FREE(n) do {if ((n) == 0){DO_ERR_EX(n,ERR_FREE_MORE);}}while(0)
21 #define CHECK_FREE_LACK(n) do {if ((n)){ERROR(ERR_FREE_LACK);}}while(0)
22 static int malloc_free_flag =0;
23 static long memory_count = 0;
24 static unsigned int error_type = 0;
25 void malloc_free_destory(void);
26 void malloc_free_init(void*pv){
27     if (malloc_free_flag) {
28         //log("module inited..",X);
29         return;
30     }
31     malloc_free_flag = 1;
32     //todo:module init...
33     error_type = 0;
34     atexit(malloc_free_destory);
35 }
36 void *c_malloc(size_t size){
37     void *re;
38     CHECK_ALLOC(memory_count);
39     re = malloc(size);
40     memory_count += (re != 0);
41     return re;
42 }
43 void c_free(void *ptr){
44     if (ptr){
45         CHECK_FREE(memory_count);
46         free(ptr);
47         memory_count--;
48     }
49     return;
50 }
51 void malloc_free_destory(void){
52     if (!malloc_free_flag) {
53         //log("module not inited..",X);
54         return;
55     }
56     malloc_free_flag = 0;
57     //todo:module destory...
58     if (error_type == 0){
59         CHECK_FREE_LACK(memory_count);
60     }
61     fprintf(stderr,"%s",error_str[error_type]);
62  
63 }

01 #include <stdlib.h>
02  
03  
04 #include "malloc_free.h"
05 #include <stdio.h>
06 #include <stdlib.h>
07  
08 typedef void (* TEST_FUNC)(void);
09  
10 static void test0(void){
11     void *p1 = 0;
12     char *p2 = 0;
13     printf("test0\n");
14     p1 =(char*)c_malloc(4);//*4);
15     p2 = (char*)c_malloc(6);
16     c_free(p1);
17     c_free(p2);
18     return//normal check
19 }
20 static void test1(void){
21     char *p1,*p2;
22     p1 = (char*)c_malloc(5);
23     p2 = (char*)c_malloc(6);
24     c_free(p1);
25     //cfree(p2);
26     return//free lack check
27 }
28 static void test2(void){
29     char *p1,*p2,*p3;
30     p1 = (char*)c_malloc(5);
31     p3 = p2 = (char*)c_malloc(6);
32     c_free(p1);
33     c_free(p2);
34     c_free(p3);
35     return//free more check
36 }
37 static void test3(void){
38     char **pp = (char**)c_malloc(sizeof(char*)*MAX_MALLOC_NUM + 1);
39     int i;
40     for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){
41         pp[i] = (char*)c_malloc(2);
42     }
43     return//alloc more check
44 }
45 #define TEST_MASK 3
46 TEST_FUNC test_a[TEST_MASK+1]= {test0,test1,test2,test3};
47    
48 int main(int argc,char *argv[]){
49     int mode;
50     printf("hello test_malloc_free_main now run...\n");
51     malloc_free_init(0);
52     if (argc < 2){
53         printf("need parameters!\n");
54         return 1;
55     }
56     mode = argv[1][0] - '0';
57     test_a[mode & TEST_MASK]();
58     malloc_free_destory();
59     printf("hello test_malloc_free_main now exit...\n");
60     return 0;
61 }

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

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

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

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

    printf("hello world!\n");

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

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

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

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

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

    下面说下malloc_free.h

    
1 #define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change
2     #define MAX_MALLOC_NUM  65535 // max malloc times ,you can change
3     #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,所以需要注意 
    
1 CHECK_ALLOC(memory_count);
2     re = malloc(size);
3     memory_count += (re != 0);
4  
5     if (ptr){
6         CHECK_FREE(memory_count);
7         free(ptr);
8         memory_count--;
9     }

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

    为什么有如下定义 
1 #define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
2 #define DO_ERR_EX(n,E) do {ERROR(E);n = 0;exit(1);} while (0)
    这是从过程化细分的角度考虑的。此处完全可以做到 
1 //    #define ERROR(n) do {error_type = ((n)<ERR_MAX_NUM?n:error_type);}while(0)
2     #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 进行赋值。而在 
1 #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。先到此,针对目标的其他动作后续处理。

转自:http://my.oschina.net/luckystar/blog/67095

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值