《[免费下载 c语言深度解剖[1]》

下载本书

添加书签

[免费下载 c语言深度解剖[1]- 第19部分


按键盘上方向键 ← 或 → 可快速上下翻页,按键盘上的 Enter 键可回到本书目录页,按键盘上方向键 ↑ 可回到本页顶部!
没有改变,只是再也没有办法使用了。

这就是 
free函数的功能。按照上面的分析,如果对 
p连续两次以上使用 
free函数,肯
定会发生错误。因为第一使用 
free函数时, 
p所属的内存已经被释放,第二次使用时已经无
内存可释放了。关于这点,我上课时让学生记住的是:一定要一夫一妻制,不然肯定出错。


malloc两次只 
free一次会内存泄漏;malloc一次 
free两次肯定会出错。也就是说,在程序
中 
malloc的使用次数一定要和 
free相等,否则必有错误。这种错误主要发生在循环使用 
malloc函数时,往往把 
malloc和 
free次数弄错了。这里留个练习:

写两个函数,一个生成链表,一个释放链表。两个函数的参数都只使用一个表头指针。

5。3。5。5,内存释放之后
既然使用 
free函数之后指针变量 
p本身保存的地址并没有改变,那我们就需要重新把 
p
的值变为 
NULL: 
p 
= 
NULL;
这个 
NULL就是我们前面所说的“栓野狗的链子”。如果你不栓起来迟早会出问题的。比如:
在 
free(p)之后,你用 
if(NULL!=p)这样的校验语句还能起作用吗?
例如: 


char*p 
= 
(char*)malloc(100); 


strcpy(p; 
“hello”); 


free(p); 
/* 
p所指的内存被释放,但是 
p所指的地址仍然不变 
*/ 


。 


if 
(NULL!= 
p) 


{ 


/*没有起到防错作用 
*/ 


strcpy(p; 
“world”); 
/*出错 
*/ 



释放完块内存之后,没有把指针置 
NULL,这个指针就成为了“野指针”,也有书叫“悬
垂指针”。这是很危险的,而且也是经常出错的地方。所以一定要记住一条:free完之后,
一定要给指针置 
NULL。
同时留一个问题:对 
NULL指针连续 
free多次会出错吗?为什么?如果让你来设计 
free
函数,你会怎么处理这个问题?

5。3。6,内存已经被释放了,但是继续通过指针来使用
这里一般有三种情况:

第一种:就是上面所说的,free(p)之后,继续通过 
p指针来访问内存。解决的办法
就是给 
p置 
NULL。

第二种:函数返回栈内存。这是初学者最容易犯的错误。比如在函数内部定义了一个
数组,却用 
return语句返回指向该数组的指针。解决的办法就是弄明白栈上变量的生命周期。


第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办
法是重新设计程序,改善对象之间的调用关系。

上面详细讨论了常见的六种错误及解决对策,希望读者仔细研读,尽量使自己对每种错
误发生的原因及预防手段烂熟于胸。一定要多练,多调试代码,同时多总结经验。


第六章函数

什么是函数?为什么需要函数?这两个看似很简单的问题,你能回答清楚吗?

6。1,函数的由来与好处
其实在汇编语言阶段,函数这个概念还是比较模糊的。汇编语言的代码往往就是从入口
开始一条一条执行,直到遇到跳转指令(比如 
ARM指令 
B、BL、BX、BLX之类)然后才
跳转到目的指令处执行。这个时候所有的代码仅仅是按其将要执行的顺序排列而已。后来人
们发现这样写代码非常费劲,容易出错,也不方便。于是想出一个办法,把一些功能相对来
说能成为一个整体的代码放到一起打包,通过一些数据接口和外界通信。这就是函数的由来。
那函数能给我们带来什么好处呢?简单来说可以概括成以下几点:

1、降低复杂性:使用函数的最首要原因是为了降低程序的复杂性,可以使用函数来隐
含信息,从而使你不必再考虑这些信息。

2、避免重复代码段:如果在两个不同函数中的代码很相似,这往往意味着分解工作有
误。这时,应该把两个函数中重复的代码都取出来,把公共代码放入一个新的通用函数中,
然后再让这两个函数调用新的通用函数。通过使公共代码只出现一次,可以节约许多空间。
因为只要在一个地方改动代码就可以了。这时代码也更可靠了。

3、限制改动带来的影响:由于在独立区域进行改动,因此,由此带来的影响也只限于
一个或最多几个区域中。
4、隐含顺序:如果程序通常先从用户那里读取数据,然后再从一个文件中读取辅助数
据,在设计系统时编写一个函数,隐含哪一个首先执行的信息。
5、改进性能:把代码段放入函数也使得用更快的算法或执行更快的语言(如汇编)来
改进这段代码的工作变得容易些。
6、进行集中控制:专门化的函数去读取和改变内部数据内容,也是一种集中的控制形

式。
7、隐含数据结构:可以把数据结构的实现细节隐含起来。
8、隐含指针操作:指针操作可读性很差,而且很容易引发错误。通过把它们独立在函

数中,可以把注意力集中到操作意图而不是集中到的指针操作本身。
9、隐含全局变量:参数传递。 
C语言中,函数其实就是一些语句的的集合,而语句又是由关键字和符号等元素组成,

如果我们把关键字、符号等基本元素弄明白了,函数不就没有问题了么?我看未必。真正要
编写出高质量的函数来,是非常不容易的。前辈们经过大量的探讨和研究总结出来一下一些
通用的规则和建议:

6。2,编码风格
很多人不重视这点,认为无所谓,甚至国内的绝大多数教材也不讨论这个话题,导致学


生入公司后仍要进行编码风格的教育。我接触过很多学生,发现他们由于平时缺乏这种意识,
养成了不好的习惯,导致很难改正过来。代码没有注释,变量、函数等命名混乱,过两天自
己都看不懂自己的代码。下面是一些我见过的比较好的做法,希望读者能有所收获。

【规则6…1】每一个函数都必须有注释,即使函数短到可能只有几行。头部说明需要包
含包含的内容和次序如下: 


/************************************************************************ 


* 
Function 
Name 
: 
nucFindThread 
* 
CreateDate 
: 
2000/01/07 
* 
Author/Corporation 
: 
yourname/yourpanyname 
* 
* 
Description 
: 
Findaproperthreadinthreadarray。 
* 
If 
it’sanewthensearchanempty。 
* 
* 
Param 
: 
ThreadNo: 
someParamdescription 
* 
ThreadStatus: 
someParam 
description 
* 
* 
ReturnCode 
: 
ReturnCodedescription;eg: 
ERROR_Fail: 
not 
find 
a 
thread 
ERROR_SUCCEED:found 
* 


* 
Global 
Variable 
: 
DISP_wuiSegmentAppID 
* 
FileStaticVariable 
: 
naucThreadNo 
* 
FunctionStaticVariable 
: 
None 
* 
* 
Revision 
History 
* 
No。 
Date 
Revised 
by 
Item 
Description 
* 
V0。5 
2008/01/07 
your 
name 
*********************/ 
staticunsignedcharnucFindThread(unsigned charThreadNo;unsigned 
char 
ThreadStatus) 
{ 
************************************************(…) ***(…) 
… 


【规则6…2】每个函数定义结束之后以及每个文件结束之后都要加一个或若干个空行。
例如: 


/************************************************************************ 


* 
……… 
* 
Function1 
Description 
* 
……… 
************************************************************************/ 
voidFunction1(……) 
{ 
… 
} 


//Blank 
Line 


/************************************************************************ 


* 
……… 
* 
Function2 
Description 
* 
……… 
************************************************************************/ 
voidFunction2(……) 
{ 
… 
} 


//BlankLine 


/************************************************************************ 


* 
……… 
* 
Function3 
Description 

* 
……… 
************************************************************************/ 
voidFunction3(……) 
{ 
… 
} 


//BlankLine

【规则6…3】在一个函数体内,变量定义与函数语句之间要加空行。
例如: 


/************************************************************************ 


* 
……… 
* 
Function 
Description 
*……… 
************************************************************************/ 
voidFunction1() 
{ 
int 
n; 


//Blank 
Line 
statement1 
……。 




【规则6…4】逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
例如: 


//Blank 
Line 


while 
(condition) 
{ 
statement1; 


//BlankLine 


if(condition) 
{ 
statement2; 


} 
else 
{ 
statement3; 


} 
//BlankLine 
statement4 


【规则6…5】复杂的函数中,在分支语句,循环语句结束之后需要适当的注释,方便区
分各分支或循环体 


while 
(condition) 
{ 
statement1; 


if(condition) 


{ 
for(condition) 
{ 


Statement2; 


}//end 
“for(condition)” 
} 
else 
{ 



statement3; 
}//”end 
if 
(condition)” 


statement4 
}//end 
“while(condition)”

【规则6…6】修改别人代码的时候不要轻易删除别人的代码,应该用适当的注释方式,
例如: 


while 
(condition) 
{ 
statement1; 


////////////////////////////////////// 
//your 
name 
; 
2008/01/07delete 
//if 
(condition) 
//{ 
// 
for(condition) 
// 
{ 
// 
Statement2; 
// 
} 
//} 
//else 
//{ 
// 
statement3; 
//} 
//////////////////////////////////////// 


/////////////////////////////////////// 
//yourname;2000/01/07 
add 
… 
new 
code 
… 
/////////////////////////////////////// 


statement4 


【规则6…7】用缩行显示程序结构,使排版整齐,缩进量统一使用 
4个字符(不使用 
TAB
缩进)。

每个编辑器的TAB键定义的空格数不一致,可能导致在别的编辑器打开你的代码乱成一
团糟。

【规则6…8】在函数体的开始、结构/联合的定义、枚举的定义以及循环、判断等语句中
的代码都要采用缩行。

【规则6…9】同层次的代码在同层次的缩进层上。

例如:

提倡的的风格不提倡的风格 
void 
Function(int 
x) 
{ 
//program 
code 
} 
void 
Function(int 
x) 
{ 
//program 
code 
} 
struct 
tagMyStruct 
struct 
tagMyStruct{ 



{ 
int 
a; 
int 
a; 
int 
b; 
int 
b; 
int 
c; 
int 
c; 
}; 
}; 
if 
(condition) 
if 
(condition){ 
{ 
//program 
code 
//program 
code 
}else{ 
} 
//program 
code 
else 
} 
{ 
//program 
code 
} 


【规则6…10】代码行最大长度宜控制在80个字符以内,较长的语句、表达式等要分成
多行书写。

【规则6…11】长表达式要在低优先级操作符处划分新行,操作符放在新行之首(以便突
出操作符)。拆分出的新行要进行适当的缩进,使排版整齐,语句可读。
例如: 


if 
((very_longer_variable1 
》= 
very_longer_variable12) 
&&(very_longer_variable3 
小提示:按 回车 [Enter] 键 返回书目,按 ← 键 返回上一页, 按 → 键 进入下一页。 赞一下 添加书签加入书架