九、malloc ,free的使用
记得曾经有个等式 为 程序=数据+算法。程序总是在处理数据,这是跑不掉的,哪怕是动下鼠标。不过有些数据的使用空间可以提前约束限制,有些则不行。例如一个函数如果返回值, 则这个类型是确定的,它的空间是确定的,(别脑袋转糊涂了,和我说,如果返回个指针,那个空间怎么是确定的?既然你说返回个指针,那么存放这个指针的空间 是固定的,我说的是这个)。显然也有些空间是无法确定的。由此,就存在一个动态申请空间的问题。也就是在程序执行中,获取新的空间。通过什么方式?C标准 在标准库里,有aligned_alloc ,calloc,malloc,realloc。先不说他们的区别。先说问题。如果你申请了,这空间可不能给别人用。你不停的申请,用银子换来的有限的内 存,无论多大,迟早用光。所以有申请,就得有释放。释放就简单了。一个函数。free。
先说说标准库函数是什么?首先不是C语言本身的东西,是用C语言编写的一些基本函数组成的库。这些“让C语言是高级语言”这句话从一纸标准变成事实。为什么这么说呢?如下的代码是不需要库函数的。
1 | int main( int argc, char *argv[]){ |
而如下的代码是需要库函数的
1 | int main( int argc, char *argv[]){ |
2 | printf ( "hello world\n" ); |
高级语言之所以高级是因为代码设计可以脱离具体硬件环境,但存在和硬件打交道时如printf,总要有动作,那么C语言的标准就要求,任何平台上的C编译 器工具,必须也面向开发者提供C语言标准所要求的,标准函数库。算是基本配置吧。任何硬件平台,要说自己支持C语言开发,都必须提供这些标准库函数的实现 方法,并且并入到编译器开发工具 包中。
但同时,各个编译器为了让你更爽,也白送你很多润滑油(增强的库函数),但是这些库函数一旦换了编译器就麻烦了。所以我尽可能的只说标准库的函数,也建议 你,除非不换编译器(那就GCC好了,是支持平台,硬件和OS,最多的了),否则不要用那些润滑油。以方便C语言能更好的移植,毕竟linux崇尚开源 嘛。上面说了4个申请空间的函数,和一个释放空间的函数,使用时需要
先谈下realloc,这个要注意,因为标准说它是unspecified的,所以可以通过各种方式实现他,由此导致你的一些代码可能移植性差,如果用他,其实你也没必要用。
这里先列出函数申明。重复说一下,函数申明的作用是让编译在发现调用这些函数时,可以根据函数的申明检测函数调用是否符合规则,如入口参数,返回参数所传递给的当前变量是否类型和数量上匹配。如果是在一个程序里的,我前面说了。默认函数都是extern,你不
,在编译时会有五花八门的警告情况。哈。连接时只要有库能查到对应函数名就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); |
谈上述差异性前,先谈下共性的地方。都是返回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){ |
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; |
10 | void my_free( void *ptr){ |
11 | 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的模拟则简单很多了。
1 | void *my_calloc( size_t nmemb, size_t size){ |
2 | void *re = my_alloc(nmemb,size); |
对于realloc的模拟则为
01 | void *my_realloc( void *ptr, size_t size){ |
02 | void *pn = my_alloc( sizeof ( size_t ),size); //这里没有考虑ptr对齐的问题。 |
04 | if (pn == 0) return ptr; |
05 | if (ptr == 0) return pn; |
06 | for (i = 0 ; i < size ; i++){ |
( 我写这写代码不是为了用的,只是为了让大家更好的理解和区别几个函数的不同作用)
由此,我们不再谈其他的动作。只谈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_ |
04 | #define ALLOC_PAGE_SIZE 4096 //mininum malloc unit sizes ,not change |
05 | #define MAX_MALLOC_NUM 65535 // max malloc times ,you can change |
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) |
21 | void malloc_free_init( void *); |
23 | void *c_malloc( size_t size); |
24 | void c_free( void *ptr); |
27 | #endif //_malloc_free_H_ |
05 | #include "malloc_free.h" |
15 | const static char error_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); |
34 | atexit (malloc_free_destory); |
36 | void *c_malloc( size_t size){ |
38 | CHECK_ALLOC(memory_count); |
40 | memory_count += (re != 0); |
43 | void c_free( void *ptr){ |
45 | CHECK_FREE(memory_count); |
51 | void malloc_free_destory( void ){ |
52 | if (!malloc_free_flag) { |
53 | //log("module not inited..",X); |
57 | //todo:module destory... |
59 | CHECK_FREE_LACK(memory_count); |
61 | fprintf (stderr, "%s" ,error_str[error_type]); |
04 | #include "malloc_free.h" |
08 | typedef void (* TEST_FUNC)( void ); |
10 | static void test0( void ){ |
14 | p1 =( char *)c_malloc(4); //*4); |
15 | p2 = ( char *)c_malloc(6); |
18 | return ; //normal check |
20 | static void test1( void ){ |
22 | p1 = ( char *)c_malloc(5); |
23 | p2 = ( char *)c_malloc(6); |
26 | return ; //free lack check |
28 | static void test2( void ){ |
30 | p1 = ( char *)c_malloc(5); |
31 | p3 = p2 = ( char *)c_malloc(6); |
35 | return ; //free more check |
37 | static void test3( void ){ |
38 | char **pp = ( char **)c_malloc( sizeof ( char *)*MAX_MALLOC_NUM + 1); |
40 | for (i = 0 ; i <= MAX_MALLOC_NUM ; i++){ |
41 | pp[i] = ( char *)c_malloc(2); |
43 | return ; //alloc more check |
46 | TEST_FUNC test_a[TEST_MASK+1]= {test0,test1,test2,test3}; |
48 | int main( int argc, char *argv[]){ |
50 | printf ( "hello test_malloc_free_main now run...\n" ); |
53 | printf ( "need parameters!\n" ); |
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" ); |
编译,运行。记得加上参数, 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。这里不强调上述语法问题。其他书上都有,标准里也有。谈下函数指针是个什么玩意。先说一下下面的代码
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个函数选择的进入呢?同时扩充模式,需要额外补充上述代码。此时用函数指针数组的方式就再好不过了。而如果只是个函数指针的变量,例如这样定义
则可以直接使用
此时就可以进入实际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 |
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); |
3 | memory_count += (re != 0); |
6 | CHECK_FREE(memory_count); |
两段代中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