一.内存管理
1.关于内存:
内存分为三个部分:静态区,栈,堆。其实堆栈就是栈,而不是堆。堆的英文是 heap;栈的英文是 stack,也翻译为堆栈。
静态区:全局初始化区,全局未初始化区。保存自动全局变量和static变量(包括 static全局和局部变量)。静态区的内容在总个程序的生命周期内都存在,由编译器在编译的时候分配。
栈:保存局部变量。栈上的内容只在函数的范围内存在,当函数运行结束,这些内容也会自动被销毁。其特点是效率高,但空间大小有限。
堆:由 malloc系列函数或 new操作符分配的内存。其生命周期由 free或 delete决定。在没有释放之前一直存在,直到程序结束。其特点是使用灵活,空间比较大,但容易出错。
2.关于常见内存错误及对策:
(1)指针没有指向一块合法的内存:
a.结构体成员指针未初始化:
例1:struct student
{
char *name;
int score;
}stu,*pstu;
int main()
{
strcpy(stu.name,"Jimy");
stu.score = 99;
return 0;
}
分析:这里定义了结构体变量 stu,但是这个结构体内部char *name这成员在定义结构体变量stu时,只是给name这个指针变量本身分配了4个字节。name指针并没有指向一个合法的地址,这时候其内部存的只是一些乱码。所以在调用strcpy函数时,会将字符串"Jimy"往乱码所指的内存上拷贝,而这块内存name指针根本就无权访问,导致出错。
解决的办法:为name指针malloc一块空间。
例2:struct student
{
char *name;
int score;
}*pstu;
int main()
{
pstu = (struct student*)malloc(sizeof(struct student));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}
分析:为指针变量pstu分配了内存,但是同样没有给name指针分配内存。错误与上面第一种情况一样,解决的办法也一样。这里用了一个malloc给人一种错觉,以为也给name指针分配了内存,其实没有。
b.没有为结构体指针分配足够的内存:
例3:int main()
{
pstu = (struct student*)malloc(sizeof(struct student*));
strcpy(pstu->name,"Jimy");
pstu->score = 99;
free(pstu);
return 0;
}
分析:为pstu分配内存的时候,分配的内存大小不合适。这里把sizeof(struct student)误写为sizeof(struct student*)。当然name指针同样没有被分配内存。解决办法同上。
(2)为指针分配的内存太小:
例4:char *p1 = “abcdefg”;
char *p2 = (char *)malloc(sizeof(char)*strlen(p1));
strcpy(p2,p1);
分析:忘了字符串常量的结束标志“\0”。这里需要注意的是,只有字符串常量才有结束标志符。下面这种写法就没有结束标志符了:
char a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};
(3)内存分配成功,但并未初始化:
犯这个错误往往是由于没有初始化的概念或者是以为内存分配好之后其值自然为 0。初始化指针变量也许看起来不那么严重,但是它确确实实是个非常严重的问题,而且往往出现这种错误很难找到原因。所以在定义一个变量时,第一件事就是初始化。
(4)内存越界:
这种错误经常是由于操作数组或指针时出现“多1”或“少1”。比如:
int a[10] = {0};
for (i=0; i<=10; i++)
{
a[i] = i;
}
(5)内存泄漏:
会产生泄漏的内存就是堆上的内存(这里不讨论资源或句柄等泄漏情况),也就是说由malloc系列函数或new操作符分配的内存。如果用完之后没有及时free或delete,这块内存就无法释放,直到整个程序终止。
3.关于malloc函数:
malloc函数的返回值是一个void类型的指针,内存分配成功之后,malloc函数返回这块内存的首地址。你需要一个指针来接收这个地址。但是由于函数的返回值是void *类型的,所以必须强制转换成你所接收的类型。
使用malloc函数同样要注意这点:如果所申请的内存块大于目前堆上剩余内存块(整块),则内存分配会失败,函数返回NULL。注意这里说的“堆上剩余内存块”不是所有剩余内存块之和,因为malloc函数申请的是连续的一块内存。
如:char *p = (char *)malloc(100);
用if(NULL != p)语句来检测内存是否分配成功。
另外,用malloc函数申请0字节内存,函数并不返回NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为0的内存。一定要小心,因为这时候 if(NULL !=p)语句校验将不起作用。
4.关于free函数:
free函数就是把这块内存和p之间的所有关系斩断。从此p和那块内存之间再无瓜葛。至于指针变量p本身保存的地址并没有改变,但是它对这个地址处的那块内存却已经没有所有权了。那块被释放的内存里面保存的值也没有改变,只是再也没有办法使用了。
malloc两次只 free一次会内存泄漏;malloc一次 free两次肯定会出错。
既然使用free函数之后指针变量p本身保存的地址并没有改变,那我们就需要重新把p的值变为NULL。如果不把p置为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。
5.关于内存已经被释放了,但是继续通过指针来使用的错误:
这里一般有三种情况:
第一种:就是上面所说的,free(p)之后,继续通过p指针来访问内存。解决的办法就是给p置NULL。
第二种:函数返回栈内存。
第三种:内存使用太复杂,弄不清到底哪块内存被释放,哪块没有被释放。解决的办法是重新设计程序,改善对象之间的调用关系。
6.关于函数参数校验:
不管什么时候,我们使用指针之前一定要确保指针是有效的。一般在函数入口处使用assert(NULL != p)对参数进行校验。在非参数的地方使用if(NULL != p)来校验。但这都有一个要求,即p在定义的同时被初始化为NULL了。比如上面的例子,即使用if(NULL != p)校验也起不了作用,因为name指针并没有被初始化为NULL,其内部是一个非NULL的乱码。
assert是一个宏,而不是函数,包含在 assert.h头文件中。如果其后面括号里的值为假,则程序终止运行,并提示出错;如果后面括号里的值为真,则继续运行后面的代码。这个宏只在Debug版本上起作用,而在Release版本被编译器完全优化掉,这样就不会影响代码的性能。
使用assert宏的地方在Release版本里面确实没有了这些校验。assert宏只是帮助我们调试代码用的,assert宏可以帮助我们定位错误,而不是排除错误。
二.函数
1.关于编码风格:
(1).每一个函数都必须有注释,即使函数短到可能只有几行。头部说明需要包含包含的内容和次序如下:
/************************************************************************
* Function Name: nucFindThread
* Create Date: 2000/01/07
* Author/Corporation: your name/your company name
*
* Description: Find a proper thread in thread array.
* Ifit’sanewthensearchanempty.
*
* Param: ThreadNo: someParam description
* ThreadStatus: someParam description
*
* Return Code: Return Code description,eg:
ERROR_Fail: not find a thread
ERROR_SUCCEED: found
*
* Global Variable: DISP_wuiSegmentAppID
* File Static Variable: naucThreadNo
* Function Static Variable: None
*
*------------------------------------------------------------------------
* Revision History
* No. Date Revised by Item Description
* V0.5 /01/07 your name … …
************************************************************************/
static unsigned char nucFindThread(unsigned char ThreadNo,unsigned char ThreadStatus)
{
…
}
(2).每个函数定义结束之后以及每个文件结束之后都要加一个或若干个空行。
(3).在一个函数体内,变量定义与函数语句之间要加空行。
(4)逻揖上密切相关的语句之间不加空行,其它地方应加空行分隔。
(5)复杂的函数中,在分支语句,循环语句结束之后需要适当的注释,方便区分各分支或循环体。
(6)修改别人代码的时候不要轻易删除别人的代码,应该用适当的注释方式。
(7)如果函数中的参数较长,则要进行适当的分行。
(8)如果代码行中的运算符比较多,用括号确定表达式的操作顺序,避免使用默认的优先级。
(9)各个源文件负责本身文件的全局变量,同时提供一对对外函数,方便其它函数使用该函数来访问变量。比如:niSet_ValueName(?);niGet_ValueName(?);不要直接读写全局变量,尤其是在多线程编程时,必须使用这种方式,并且对读写操作加锁。
(10)如果参数是指针,且仅作输入参数用,则应在类型前加const,以防止该指针在函数体内被意外修改。
(11)在函数体的“入口处”,对参数的有效性进行检查。尤其是指针参数,尽量使用assert宏做入口校验, 而不使用if语句校验。
(12)return语句不可返回指向“栈内存”的“指针”。
2.关于递归函数:
例:不允许调用库函数,也不允许使用任何全局或局部变量编写int my_strlen(char *strDest);
代码如下:
int my_strlen( const char* strDest )
{
assert(NULL != strDest);
if ('\0' == *strDest)
{
return 0;
}
else
{
return (1 + my_strlen(++strDest));
}
}
第一步:用 assert宏做入口校验。
第二步:确定参数传递过来的地址上的内存存储的是否为'\0'。如果是,表明这是一个空字符串,或者是字符串的结束标志。
第三步:如果参数传递过来的地址上的内存不为'\0',则说明这个地址上的内存上存储的是一个字符。既然这个地址上存储了一个字符,那就计数为1,然后将地址加1 个char类型元素的大小,然后再调用函数本身。如此循环,当地址加到字符串的结束标志符'\0'时,递归停止。
函数调用的开销比循环来说要大得多,所以,递归的效率很低,递归的深度太大甚至可能出现错误(比如栈溢出) 。所以,平时写代码,不到万不得已,尽量不要用递归。即便是要用递归,也要注意递归的层次不要太深,防止出现栈溢出的错误;同时递归的停止条件一定要正确,否则,递归可能没完没了。
三.文件结构
1.关于文件内容的一些风格:
(1)每个头文件和源文件的头部必须包含文件头部说明和修改记录。源文件和头文件的头部说明必须包含的内容和次序如下:
/************************************************************************
* File Name: FN_FileName.c/ FN_FileName.h
* Copyright: - XXXX Corporation,All Rights Reserved.
* Module Name: Draw Engine/Display
*
* CPU: ARM7
* RTOS: Tron
*
* Create Date: /10/01
* Author/Corporation: WhoAmI/your company name
*
* Abstract Description: Place some description here.
*
*-----------------------Revision History---------------------------------
* No Version Date Revised By Item Description
* 1 V0.95 08.05.18 WhoAmI abcdefghijklm WhatUDo
*
************************************************************************/
(2)各个源文件必须有一个头文件说明,头文件各部分的书写顺序下:
No.Item
1Header File Header Section
2Multi-Include-Prevent Section
3Debug Switch Section
4Include File Section
5Macro Define Section
6Structure Define Section
7Prototype Declare Section
其中 Multi-Include-Prevent Section是用来防止头文件被重复包含的。
如下例:#ifndef __FN_FILENAME_H
#define __FN_FILENAME_H
#endif
其中“FN_FILENAME”一般为本头文件名大写,这样可以有效避免重复,因为同一工程中不可能存在两个同名的头文件。
/************************************************************************
* File Name: FN_FileName.h
* Copyright: - XXXX Corporation,All Rights Reserved.
* Module Name: Draw Engine/Display
*
* CPU: ARM7
* RTOS: Tron
*
* Create Date: /10/01
* Author/Corporation: WhoAmI/your company name
*
* Abstract Description: Place some description here.
*
*----------------------------------------Revision History---------------------------------
* No Version Date Revised By Item Description
* 1 V0.95 08.05.18 WhoAmI abcdefghijklm WhatUDo
*
************************************************************************/
/************************************************************************
* Multi-Include-Prevent Section
************************************************************************/
#ifndef __FN_FILENAME_H
#define __FN_FILENAME_H
/************************************************************************
* Debug switch Section
************************************************************************/
#define D_DISP_BASE
/************************************************************************
* Include File Section
************************************************************************/
#include "IncFile.h"
/************************************************************************
* Macro Define Section
************************************************************************/
#define MAX_TIMER_OUT (4)
/************************************************************************
* Struct Define Section
************************************************************************/
typedef struct CM_RadiationDose
{
unsigned char ucCtgID;
char cPatId_a[MAX_PATI_LEN];
}CM_RadiationDose_st, *CM_RadiationDose_pst;
/************************************************************************
* Prototype Declare Section
************************************************************************/
unsigned intMD_guiGetScanTimes(void);
…
…
#endif
(3)源文件各部分的书写顺序如下:
No.Item
1Source File Header Section
2Debug Switch Section
3Include File Section
4Macro Define Section
5Structure Define Section
6Prototype Declare Section
7Global Variable Declare Section
8File Static Variable Define Section
9Function Define Section
/*************************************************************************
* File Name : FN_FileName.c
* Copyright : - XXXX Corporation, All Rights Reserved.
* Module Name : Draw Engine/Display
*
*CPU : ARM7
*RTOS : Tron
*
* Create Date : /10/01
* Author/Corporation : WhoAmI/your company name
*
* Abstract Description : Place some description here.
*
*-----------------------Revision History---------------------------------
* No Version Date Revised By Item Description
* 1 V0.95 00.05.18 WhoAmI abcdefghijklm WhatUDo
*
************************************************************************/
/************************************************************************
* Debug switch Section
************************************************************************/
#define D_DISP_BASE
/************************************************************************
* Include File Section
************************************************************************/
#include "IncFile.h"
/************************************************************************
* Macro Define Section
************************************************************************/
#define MAX_TIMER_OUT (4)
/************************************************************************
* Struct Define Section
************************************************************************/
typedef struct CM_RadiationDose
{
unsigned char ucCtgID;
char cPatId_a[MAX_PATI_LEN];
}CM_RadiationDose_st, *pCM_RadiationDose_st;
/************************************************************************
* Prototype Declare Section
************************************************************************/
unsigned int MD_guiGetScanTimes(void);
/************************************************************************
* Global Variable Declare Section
************************************************************************/
extern unsigned int MD_guiHoldBreathStatus;
/************************************************************************
* File Static Variable Define Section
************************************************************************/
static unsigned int nuiNaviSysStatus;
/************************************************************************
* Function Define Section
************************************************************************/
(4)需要对外公开的常量放在头文件中,不需要对外公开的常量放在定义文件的头部。