低级错误案例集汇总
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
低级错误案例集汇总
TOP1 资源泄漏
资源泄漏(包括内存泄漏)是代码Review中最常见的错误之一,申请的每个资源必须明确由谁负责释放,何时释放,在何处释放;在异常/错误/返回处理中,保持清醒的头脑,清理战场。
此处的资源还包括信号量、定时器、文件句柄等系统资源。
案例1.1
【问题描述】
宏里面有return语句导致内存泄漏案例一。
【问题分析】
1) 错误代码:
/*定义宏MODEL_ASSERT_RETFAIL*/
#define MODEL_ASSERT_RETFAIL (X)\
{\
if(X不合法)\
return;\
}
….//do something
MDSTrafficMsg* pMsg = VOS_AllocMsg( PID_MD, usLength );
if (NULL_PTR = = pMsg )
{
return ;
}
MDSDataListenerMgr *pDataListener = MDSDataInitalListenerMgr();
MODEL_ASSERT_RETFAIL(pDataListener);
2)分析:
使用宏MODEL_ASSERT_RETFAIL检查pDataListener是否合法,如果不合法,则直接返回,一旦返回,将导致前面通过指针pMsg申
请到的消息包资源泄漏。
【纠正方法】
在宏MODEL_ASSERT_RETFAIL分支判断return前加上VOS_FreeMsg(PID_MD, pMsg ):该方法代码不够清晰,当用户看宏定义时,对VOS_FreeMsg(PID_MD, pMsg )不清楚还要跳回来看前面的代码。
设定该宏有返回值(指针不为空返回VOS_True,否则为VOS_False),将宏的return语句写在宏使用后(判断指针pDataListener合法性),若宏返回VOS_False释放pMsg并返回主调函数:该方法在遇到只判断一个指针的合法性时,浪费代码行、降低代码飞检效率且可能存在宏描述歧义等问题,简单的判断建议不使用宏。
【经验教训】
在XX版本的一个新模块的开发中,在发布之前进行大话务量测试验证时,发现系统内存资源不足,当时发布在即,经过协调多个技术专家封闭攻关,花了三天时间终于发现问题所在,人力成本高达3000元,对于内存使用,要确保释放闭环,所有异常退出点都需要释放内存。
案例1.2
【问题描述】
宏里面有return语句导致内存泄漏案例二。
【问题分析】
1) 错误代码:
头文件中的宏定义如下:
#define NODE_RETURN_ERROR (p) \
{\
if (NULL == p) \
{\
VOS_RECORD_ERROR(p);\
return NULL;\
}\
}
文件中有个函数有如下代码段:
...//do something
pNode = (Node_Head_S *)malloc(sizeof(Node_Head_S));
NODE_RETURN_ERROR (pNode);//第一次使用宏
pBody = (Node_Body_S *)malloc(sizeof(Node_Body_S));
NODE_RETURN_ERROR (pBody); //第二次使用宏
...//do something
2)分析:
当通过指针pBody申请内存,然后通过宏NODE_RETURN_ERROR来判断是否申请成功,如果申请失败,则在宏NODE_RETURN_ERROR里面就直接返回了,这样导致通过指针pNode申请的内存泄漏了。
【纠正方法】
不使用宏,或将宏中的return语句写到宏调用后。
案例1.3
【问题描述】
异常出口没有释放应该释放的内存案例一。
【问题分析】
1) 错误代码:
// GetBuff函数的作用是申请动态内存
pMsgDB_DEV = (PDBDevMsg)GetBuff( sizeof( DBDevMsg ), __LINE__);
if ( NULL= = pMsgDB_DEV )
{
return;
}
// GetBuff函数的作用是申请动态内存
pMsgDBApp_To_Logic = (LPDBSelfMsg)GetBuff( sizeof(DBSelfMsg), __LINE__ );
if ( NULL = = pMsgDBApp_T o_Logic )
{
return;
}
2)分析:
在第2个return处,pMsgDB_DEV指向的内存丢失。
【纠正方法】
在第2个return处增加释放内存的操作。
【经验教训】
函数中有动态申请内存,要在函数范围内检查所有return语句是否释放该return语句前所有动态申请的内存。
案例1.4
【问题描述】
申请过的内存的指针没有释放,又申请新的内存给它。
【问题分析】
1) 错误代码:
/* 申请新的内存大小 */
pTmp = VOS_Malloc( pMacroEdit->ulMacroLen + 1 ) ;
if( NULL = = pTmp )
{
return FAILURE ;
}
VOS_MemSet( pTmp , '\0' , pMacroEdit->ulMacroLen + 1 );
......
/*又申请新的内存*/
pTmp = VOS_Malloc(sizeof(TTTT_RPC_MSG_S));
if( NULL = = pTmp )
{
return FAILURE ;
}
…….
VOS_Free(pTmp);
2)分析:
第一次申请的内存没有释放就使用同一个指针又申请了内存。
这样第一次申请的内存就永远没办法释放了,造成了内存泄漏。
【纠正方法】
第2次申请内存前,先释放掉第1次申请的内存。
同时我们应该注意到这个案例中对于申请的内存使用的指针变量是个临时变量,其命名没有十分明确的承载其所指向内存的含义,这样命名是不规范的,也许恰恰就是因为这点,才引发了后面继续直接使用.
案例1.5
【问题描述】
异常出口没有释放应该释放的资源。
【问题分析】
1) 错误代码:
intr_lock();
/*填充消息*/
ulResult = DEV_MA_FillMsg( pMsg ,ucPrimID, usBIndex, ucSerailID);
if( ulResult != MSP_RETURN_NO_ERR )
{
VOS_FreeMsg( PID_DEV, pMsg );
return ulResult;
}
intr_unlock();
……
2)分析:
return前没有调用intr_unlock()释放中断信号量。
【纠正方法】return前释放中断信号量。
案例1.6
【问题描述】
分支考虑不全,导致某些分支中内存泄漏。
【问题分析】
1) 错误代码:
/*创建消息,并放入消息队列中,假设处理成功*/
pMsg = CreateQueueMsg (j);
mu = SortOperation(pMsg);
if (mu <= 0) {
ret = 0;
}
else if (mu <= 5){
ret = SendMsgToMu(pMsg, mu-1);
}
if (ret <= 0) {
/*从队列中删除消息,并释放*/
RemoveQueueMsg(j);
}
2)分析:
mu>5的分支,根本没有考虑,内存泄漏。
【纠正方法】
增加mu>5时对内存的释放处理。
案例1.7
【问题描述】
多个判断放在一起进行导致内存泄漏。
【问题分析】
1) 错误代码:
char* pszInfoBuf1 = VOS_NULLPTR;
char* pszInfoBuf2 = VOS_NULLPTR;
pszInfoBuf1 = (char*)VOS_Malloc(MID_BVLAN, ulBuffLen); pszInfoBuf2 = (char*)VOS_Malloc(MID_BVLAN, ulBuffLen);
if ( (pszInfoBuf1 = = NULL) || (pszInfoBuf2 = = NULL ) )
{
return VOS_ERR;
}
2)分析:
当pszInfoBuf1申请成功,但pszInfoBuf2申请失败时,if语句被执行,pszInfoBuf1指向的内存泄漏了。
【纠正方法】
将两处申请内存后的有效性判断分开进行。
案例1.8
【问题描述】
内存重复释放可能产生不可预知的后果。
【问题分析】
1) 错误代码:
void NbvmConfirmBrdVer(NBOM_TRANS *pstTrans)
{
…………
if (NULL != pstTrans->pbDynMem)
{
pstSelfBoardBootRomInfo =
(NBVM_SELF_BOARD_BOOTROM_INFO*)(void*)pstTrans->p bDynMem;
}
NBVM_MEM_FREE(pstSelfBoardBootRomInfo);
NBVM_MEM_FREE(pstBrdAllSwInfo);
NBVM_MEM_FREE(pstTrans->pbDynMem);
NBVM_MEM_FREE(pstTrans->pPrivatePtr);
}
2)分析:
上述红色代码造成了pstTrans->pbDynMem所指向的内存重复
释放。
【纠正方法】
内存重复释放可能产生不可预知的后果:假如任务A申请了内存块M,使用后释放M;系统可能把空闲的M分配给了任务B,当任务A重复释放M后,把本来属于任务B的该内存块误释放;此时系统认为空闲的内存块M又可能分配给任务C,C随后对这个内存块的写操作对于任务B来说就是非法操作,可能导致任务B运行异常。
案例1.9
【问题描述】
系统运行过程中,出现堆内存不足。
【问题分析】
1)错误代码
typedef struct tagWordStat{
char * pszWord;
ULONG ulSum;
float frate;
struct tagWordStat * psnext;
}WORDSTAT_S, *PWORDSTAT_S;
…
psWordName = (char *)malloc(ulCurWordLen + 1);
if(NULL == psWordName)
{
return VOS_ERR;
}
/*获取字符串*/
VOS_strncpy(psWordName, strName, ulCurWordLen);
...
/*建立新节点*/
pstWordStat = (PWORDSTAT_S)VOS_Malloc(sizeof(WORDSTAT_S));
if ( NULL == pstWordStat )
{
VOS_Free(psWordName);
return STAT_ERR;
}
memset(pstWordStat, 0, sizeof(WORDSTAT_S));
pstWordStat->pszWord = psWordName;
psWordName = NULL;
pstWordStat->ulSum = 1;
pstWordStat->psnext = NULL;
ulTotalWordNum ++;
...
/*释放节点*/
VOS_Free(pstWordStat);
...
2)分析
在释放节点时,只释放申请的部分内存。
在这段代码中,字符串存放申请了一段内存,建立链表的节点又申请了内存,最后只释放了链表节点内存,没有释放字符串存放时申请的内存。
【纠正方法】
先释放结构体内指针成员申请的内存,再释放结构体指针指向的内存。
释放结构体节点内存时,一定要搞清楚节点内部是否还有需要释放的内存,否则会导致这段内存永远得不到释放。
TOP2 内存越界
所谓内存越界就是申请了内存,使用时超出了申请的范围。
发生内存越界的情况,往往出错的地方不是真正内存越界的地方,带来的影响滞后,而且难以定位,需要程序员养成良好编程习惯,对指针,内存拷贝,字符串操作等注意前后空间的变化,可以做一些必要保护。
案例2.1
【问题描述】
数组下标访问越界。
【问题分析】
1) 错误代码:
/* 初始化Q922的I帧链路索引地址表 */
ulSize = sizeof(INDEX_Q922IFRAME_MAPPING_STRU **) * DCOM_MAXSERVICEQ922LINK;
// g_ppstQ922IFrameIndex[]申请的长度是DCOM_MAXSERVICEQ922LINK
g_ppstQ922IFrameIndex = (INDEX_Q922IFRAME_MAPPING_STRU
**)VOS_MemAlloc(VOS_PID_COMM,
STATIC_DOPRA_MEM_PT, ulSize);
...
VOS_UINT32 Q922_AddLink(......)
{
...
if (usLinkNo > DCOM_MAXSERVICEQ922LINK)
{
ulRet = VOS_ERRNO_Q922CFG_ADDLINK_LINKNO_INVALID;
return ulRet;
}
/* 初始化Q922的I帧映射表的信息 */
ulSize = sizeof(INDEX_Q922IFRAME_MAPPING_STRU);
g_ppstQ922IFrameIndex[usLinkNo] = (INDEX_Q922IFRAME_MAPPING_STRU
*)VOS_MemAlloc(VOS_PID_COMM,
DYNAMIC_DOPRA_MEM_PT,ulSize);
if (VOS_NULL_PTR == g_ppstQ922IFrameIndex[usLinkNo]) {
.....
ulRet = VOS_ERRNO_Q922CFG_INIT_ALLOC_IFRAMEMAP_FAILED;
return ulRet;
}
...
}
2)分析:
造成内存写越界,单板复位。
【纠正方法】
内存申请最大长度是DCOM_MAXSERVICEQ922LINK,最多访问到
g_ppstQ922IFrameIndex[DCOM_MAXSERVICEQ922LINK-1],判断应改成if (usLinkNo >= DCOM_MAXSERVICEQ922LINK) 案例2.2
【问题描述】
版本倒换,主控板一起来就出现指令异常,单板复位
【问题分析】
1) 错误代码:
CHAR sLogInfo[1024];
UCHAR szTmp[10]; /*只分配了10个字节*/
char* pTemp = sLogInfo;
…….
while(*pTemp != '-')
{
szTmp[i] = *pTemp; /*没有找到-,继续进行,导致堆栈被写坏*/
pTemp++;
i++;
}
szTmp[i] = 0;
2)分析:
当数组下标i大于等于9时,不能再继续循环,否则越界访问。
【纠正方法】
增加判断:i等于8时,跳出循环, 对于while或者for循环里操作数组下标要做下标的保护,“小心驶得万年船”
案例2.3
【问题描述】
入参检测失败后不退出函数,仍继续执行后面逻辑处理,导致后面使用数组下标越界,在异常引用情况下导致死机。
【问题分析】
1) 错误代码:
void FAR Check_ImsDataTab_by_timer (CR ims_data_tab_cr)
{
……
if ((ims_data_tab_cr) >= MAX_IMS_DATA_CR) ——A
{
CCB_stop_timer_by_ftr(ims_data_tab_cr, EN_CCB_FTR_ID_RF,
EN_RF_CHECK_SINGLE_IMSDATA_TIMER); —>后面需要return,退出函数
}
if ((MAX_SIPSL_CR == g_ims_data_table[ims_data_tab_cr].sip_cr) ——B
&& (MAX_CCB(LOCAL_MODULE) == g_ims_data_table[ims_data_tab_cr].ccb_cr)) {
……
return;
}
……
}
2)分析:
当A处的ims_data_tab_cr 入参检查失败后不退出而继续进行下
面的逻辑处理,导致B 处引用ims_data_tab_cr作为数组下标越界【纠正方法】
在入参检测中失败应做退出函数处理,A处逻辑判断中增加return
案例2.4
【问题描述】
系统运行过程中突然复位。
【问题分析】
1) 错误代码:
while ( _isspace( *szString ) )
{
/* 跳过所有空格字符 */
szString ++; //szString为函数的输入参数
}
if ('\0' == *szString )
{
return FAILURE ;
}
/*'='的左边变量赋值*/
szStr = VOS_Malloc( 20 ) ;//申请内存大小为给20
if( NULL == szStr )
{
return MACRO_MEM_SHORTAGE ;
}
VOS_MemSet(szStr, '\0', 20) ;
VOS_StrCpy( szStr, szString ) ;
2)分析:
这种内存写越界问题的表面现象是不确定的。
该问题的原因就是没有检查szString字符串的长度,最后一代码中,字符串拷贝时发生写越界。
【纠正方法】
规避这种问题的办法是拷贝字符串是采用DOPRA平台提供的安全拷贝函数
CHAR * VOS_strncpy( CHAR *dst, const CHAR *src, ULONG n )。
案例2.5
【问题描述】
Sprintf使用不当导致内存访问越界
【问题分析】
1) 错误代码:
char pszInfoBuf[250];
sprintf(pszInfoBuf,”*** File:%s Line:%d ****”,__FILE__,__LINE__); 2)分析:
“__FILE__”在预编译时,被编译时的目录名和源文件名代替,但目录和文件名的长度可变,很可能超出250个字节,导致内存越界【纠正方法】
将sprintf替换成安全函数snprintf,指定缓冲区大小INFOBUF_SIZE,确保内存不会越界
snprintf(pszInfoBuf,INFOBUF_SIZE -1,”*** File:%s Line:%d
****”,__FILE__,__LINE__);
pszInfoBuf[INFOBUF_SIZE -1]=‘\0’;
【经验教训】
C语言提供的字符串库函数sprintf /vsprintf/strcpy/strcat/gets 等非常危险,很容易导致内存越界,应该使用安全的字符串库函数snprintf/strncpy/strncat/fgets,指定操作的内存大小。
对C++语言,应该使用相应的类库如std::string,std::stringstream,
boost::lexical_cast操作字符串,而不要直接调用C语言的字符串函数。
案例2.6
【问题描述】
Strcpy使用不当导致内存访问越界。
【问题分析】
1) 错误代码:
char pszInfoBuf[32];
strcpy(pszInfoBuf,pMsg);
2)分析:
定义的pszInfoBuf共32字节,pMsg是从网络上接收的数据包,可能超出32个字节,导致内存越界
【纠正方法】
将strcpy替换成安全函数strncpy ,指定缓冲区大小INFOBUF_SIZE,确保内存不会越界strncpy (pszInfoBuf,pMsg, INFOBUF_SIZE - 1);
pszInfoBuf[INFOBUF_SIZE - 1]=‘\0’;
案例2.7
【问题描述】
字符串拼接,忘记后面的\0.
【问题分析】
1) 错误代码:
ulNewSize=VOS_strlen(“\r\n”)+VOS_strlen(pRevData) + VOS_strlen(pszVlanInfo)+ VOS_strlen(pszQinQinfo);
pszDisInfo = VOS_Malloc(MID_BVLAN, ulNewSize);
......//拷贝一些内容给pszDisInfo
VOS_ strcat (pszDisInfo, pszQinQinfo); //字符串连接
2)分析:
以ulNewSize为长度申请内存,但忘记字符串结尾必须有一个’\0’,调用strcat时拷
贝了字符串本身和最后的’\0’,导致内存越界
【纠正方法】
计算ulNewSize时,增加1个字节,用于存放’\0’.
案例2.8
【问题描述】
数组下标越界。
【问题分析】
1) 错误代码:
// pstPSCCfgInfo->strPara.ucNumOfCID超过了数组下标的最大值。
for (ulCount = 0; ulCount < pstPSCCfgInfo->strPara.ucNumOfCID; ulCount++) {
pPSCTable->strPara.stCidInfo[ulCount].bitCid =
pstPSCCfgInfo->strPara.stCidInfo[ulCount].bitCid;
}
2)分析:
外部配入的pstPSCCfgInfo->strPara.ucNumOfCID值超过了定义的最大值
SLP_MAX_PSC_CID_NUM。
由于代码未加保护,导致数组下标越界。
致BBI单板复位。
【纠正方法】
增加判断:
if (SLP_MAX_PSC_CID_NUM < pstPSCCfgInfo->strPara.ucNumOfCID)
{
pstPSCCfgInfo->strPara.ucNumOfCID = SLP_MAX_PSC_CID_NUM;
}
【经验教训】
对于外部输入的数组下标要作检查,避免内存越界
案例2.9
【问题描述】
整形转换成字符串时,没有注意到\0结尾。
【问题分析】
1) 错误代码:
//使用itoa()将整型数转换为字符串时:
char szT empShold[10] ;
itoa(ulProcFrecy,szTempShold,10);
2)分析:
szTempShold是以‘\0’结尾的字符数组,只能存储9个字符,而ulProcFrecy的最大值可达到10位,导致符数组szTempShold越界。
【纠正方法】
将数组设置成11位
【经验教训】
按最大的可能分配空间,一个UL(32位)最大值表示成字符串(加\0)最多11位。
TOP3 指针使用
对于全局指针,指针所指向的空间释放后,必须把指针赋为空,防止后续对此的引用。
因为一般根据指针是否非空来判断其有效性。
案例3.1
【问题描述】
系统运行过程中无规律复位。
【问题分析】
1) 错误代码:
/*定义一个全局指针变量*/
DHCP_HEAD_S *g_stDhcpHead;
…
void funcA(void)
{
…
g_stDhcpHead = (char*)VOS_Malloc(sizeof(DHCP_HEAD_S ));
if(NULL==g_stDhcpHead)
{
return ERR;
}
…
/*经过一些操作后,释放内存*/
VOS_Free(g_stDhcpHead );
…
}
…
void funcB(void)
{
…
/*指针不为空,则往里面写字符串*/
if(NULL !== g_stDhcpHead )
{
VOS_Memcpy(g_stDhcpHead,str);
…
}
}
2)分析:
在函数A中通过指针g_stDhcpHead申请了一段内存,使用完毕后释放了,但是没有将指针置为空,刚好此时函数B也用到了指针g_stDhcpHead,当其不为空时就往里面拷贝一个字符串。
此时g_stDhcpHead申请的内存空间释放后,操作系统将其分配给了其它任务,所以函
数B往这段内存拷贝字符串时,破坏了其它任务的内存,导致系统复位。
【纠正方法】
通过指针申请内存,释放后一定要记得将指针置为空。
野指针的问题是非常难定位的,因为这种问题表现出来的现象每次都不一致,
只能通过代码走读的方法来定位。
案例3.2
【问题描述】
采用错误的逻辑运算进行参数校验,导致系统复位。
【问题分析】
1) 错误代码:
ULONG L3API_PtmFlowTransmitFrame(MBUF_S *pstMBuf, VOID* pstEtharpFlowPara)
{
…/*定义一些局部变量*/
…
/*函数入口参数合法性判断*/
if((NULL = = pstMBuf) && (NULL = = pstEtharpFlowPara)) {
return VOS_ERR;
}
…
}
2).分析:
函数入口参数校验的逻辑有问题,该逻辑判断采用逻辑与操作,导致传入的两个指针参数中有一个为空指针时,函数还会继续下面的操作,从而对空指针进行了操作,导致系统异常。
【纠正方法】
将判断逻辑修改成逻辑或操作。