废话不说,直接进入正题,本文承接先前发布的《C/C++编程新手错误语录》 (/Article/base/200611/19227.html ),继续归纳错误语录。(8)“我想用malloc”、“我用不好malloc” 来看看一个变态程序:
/* xx.c:xx模块实现文件 */ int *pInt; /* xx模块的初始化函数 */ xx_intial() { pInt = ( int * ) malloc ( sizeof( int ) ); ... } /* xx模块的其他函数(仅为举例)*/ xx_otherFunction() { *Int = 10; ... } 这个程序定义了一个全局整型变量指针,在xx模块的初始化函数中对此指针动态申请内存,并将pInt指向该内存首地址,并在xx模块的其他函数中都使用pInt指针对其指向的整数进行读取和赋值。 这个程序让我痛不欲生了好多天,扼腕叹息!这是我母校计算机系一位硕士的作品!作者为了用上malloc,拼命地把本来应该用一个全局整型变量摆平的程序活活弄成一个全局整型指针并在初始化函数中“动态”申请内存,自作聪明而正好暴露自己的无知!我再也不要见到这样的程序。 那么malloc究竟应该怎么用?笔者给出如下规则: 规则1 不要为了用malloc而用malloc,malloc不是目的,而是手段; 规则2 malloc的真正内涵体现在“动态”申请,如果程序的特性不需动态申请,请不要用malloc; 上面列举的变态程序完全不具备需要动态申请的特质,应该改为:
/* xx.c:xx模块实现文件 */ int example; /* xx模块的初始化函数 */ xx_intial() { ... } /* xx模块的其他函数(仅为举例) */ xx_otherFunction() { example = 10; ... } "我想用malloc""我用不好malloc" - "函数add编译生成的符号就是add" - "没见过在C语言中调用C++的函数""C/C++不能调用Basic、Pascal语言的函数" - "英语、数学不好就学不好C/C++" - "C++太难了,我学不会" - 规则3 什么样的程序具备需要动态申请内存的特质呢?包含两种情况: (1)不知道有多少要来,来了的又走了 不明白?这么说吧,譬如你正在处理一个报文队列,收到的报文你都存入该队列,处理完队列头的报文后你需要取出队列头的元素。 你不知道有多少报文来(因而你不知道应该用多大的报文数组),这些来的报文处理完后都要走(释放),这种情况适合用malloc和free。 (2)慢慢地长大 譬如你在资源受限的系统中编写一文本编辑器程序,你怎么做,你需要这样定义数组吗?
char str[10000];
不,你完全不应该这么做。即使你定义了一个10000字节大的字符串,用户如果输入10001个字符你的程序就完完了。 这个时候适合用malloc,因为你根本就不知道用户会输入多少字符,文本在慢慢长大,因而你也应慢慢地申请内存,用一个队列把字符串存放起来。 那么是不是应该这样定义数据结构并在用户每输入一个字符的情况下malloc一个CharQueue空间呢?
typedef struct tagCharQueue { char ch; struct tagCharQueue *next; }CharQueue; 不,这样做也不对!这将使每个字符占据“1+指针长度”的开销。 正确的做法是:
typedef struct tagCharQueue { char str[100]; struct tagCharQueue *next; }CharQueue; 让字符以100为单位慢慢地走,当输入字符数达到100的整数倍时,申请一片CharQueue空间。 规则4 malloc与free要成对出现 它们是一对恩爱夫妻,malloc少了free就必然会慢慢地死掉。成对出现不仅体现在有多少个malloc就应该有多少个free,还体现在它们应尽量出现在同一函数里,“谁申请,就由谁释放”,看下面的程序:
char * func(void) { char *p; p = (char *)malloc(…); if(p!=NULL) …; /* 一系列针对p的操作 */ return p; } /*在某处调用func(),用完func中动态申请的内存后将其free*/ char *q = func(); … free(q); 上述代码违反了malloc和free的“谁申请,就由谁释放”原则,代码的耦合度大,用户在调用func函数时需确切知道其内部细节!正确的做法是:
/* 在调用处申请内存,并传入func函数 */ char *p=malloc(…); if(p!=NULL) { func(p); … free(p); p=NULL; } /* 函数func则接收参数p */ void func(char *p) { … /* 一系列针对p的操作 */ } 规则5 free后一定要置指针为NULL,防止其成为“野”指针(9)“函数add编译生成的符号就是add”
int add(int x,int y) { return x + y; } float add(float x,float y) { return x + y; } "我想用malloc""我用不好malloc" - "函数add编译生成的符号就是add" - "没见过在C语言中调用C++的函数""C/C++不能调用Basic、Pascal语言的函数" - "英语、数学不好就学不好C/C++" - "C++太难了,我学不会" - 即便是在C语言中,add函数被多数C编译器编译后在符号库中的名字也不是add,而是_add。而在C++编译器中,int add(int x,int y)会编译成类似_add_int_int这样的名字(称为“mangled name”),float add(float x,float y)则被编译成_add_float _float,mangled name包含了函数名、函数参数数量及类型信息,C++依靠这种机制来实现函数重载。 所以,在C++中,本质上int add( int x, int y )与float add( float x, float y )是两个完全不同的函数,只是在用户看来其同名而已。 这就要求初学者们能透过语法现象看问题本质。本质上,语言的创造者们就是在玩各种各样的花样,以使语言具备某种能力,譬如mangled name花样的目的在于使C++支持重载。而C语言没有玩这样的花样,所以int add( int x, int y )与float add( float x, float y )不能在C程序中同时存在。(10)“没见过在C语言中调用C++的函数”、“C/C++不能调用Basic、Pascal语言的函数” 这又是一个奇天下之大怪的问题,“打死我都不相信C、C++、basic、pascal的函数能瞎调来调去”,可是有句话这么说: 没有你见不到的,只有你想不到的! 既然芙蓉姐姐也有其闻名天下的道理,那么C、C++、Basic、Pascal的函数为什么就不能互相调用呢? 能!
[1] [2] 下一页