如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
目录
1.简单就是美
3.字节序
4.函数参数
5.返回值
6.强制类型转换
a
8.字符串
9.资源释放
规范
11.临界资源保护
1.简单就是美
优先级搞不清楚用括号
复合语句太罗嗦,拆成几行来写
编码的三不原则
不要挑战自己的记性
不要挑战自己的耐心
不要挑战编译器的水平
编码的三用原则
能用简单句的,就不要用复杂的技巧
能用成熟代码的,就不要再来一套
能用上工具的,就一定要机械化
Structxxx
{
charcA;
shortsB;
longlC;
}
voidmain()
{
char*pchar;
pchar=(char*)malloc(7);/*1—魔鬼的数字;2—申请失败后怎么办?*/
mencpy(pchar,“abcdefgh”,sizefo(xxx));/*3—内存*/
printf(“%sn”,pchar);/*缺少字符串结束符必越界*/
return;/*5—退出前没有释放内存*/
}
数据结构是C语言的基础。C语言的灵活性很大,程度上在于其数据结构的灵活性。要用好
的数据结构,首先要掌握数据结构的大小的计算,系统的每个数据机构,每个变量都会分配
到一个对应的存储空间,这个存储空间的大小就是数据结构的尺寸。
sizeof为编译时的一元运算符,可用来计算任一对象的大小
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
sizeof的结果是编译时的常量
sizeof不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据
类型。如未知存储大小的数组类型,未知内容的结构或联合类型,void类型等。
3.字节序
X86系统
voidQosConfigPolicy(xxx)
{
ulDestIP=从命令行读取用户配置的参数;
pQosPoliscy->ulDestIP=ulDestIP;
return;
}
主机处理
voidQosClassify(xxx)
{
Plp=(IP-S*)pData;
If(pQosPolicy->ulDestIP==plp->ulDestIP)
{
Vos_HTONL(pQosPolicy->ulDestIP;
DoSomething();
}
Return;
}
由于历史的原因,世界存在两种字节序标准——BigEndian和LittleEndian。PowerPC是
大头,X86是小头。有些CPU可以通过寄存器设置支持不同的字节序。如MIPS
BigEndian——高位在低字节,地位在高字节
LittleEndian——低位在低字节,高位在高字节
e.g.0x345678大头内存从低到高存放次序00,34,56,78;小头内存从低到高存放次序
78,56,34,00
字节序问题广泛存在于设备与设备之间,单板与单板之间,单板与底层之间,只要两个处理
单元的字节序不同,这个问题就存在。为了解决不同字节序的处理单元之间的同信问题,世
界上定义了主机序和网络序的概念,网络序主要用于信息传递,一般不用于计算,其字节顺
序与大头一致。
除在编码时紧绷这根弦之外,我们在器件选择是主机序与网络序一致的芯片,同一设备的不
同单板使用相同的字节序。并优先选择支持大头的芯片,这样即使不能彻底解决问题,也可
以彻底规避问题。
4.函数参数
C语言中,函数通过返回值和参数与调用者交换信息。函数参数自身占用的存储单元在堆栈
中分配。入口参数指向的数组或地址,在函数入口处拷贝到堆栈区中,因此对函数参数所在
存储单元的直接修改不会作用到函数之外,而对参数存储单元中存放的地址指向的存储空间
的修改,则会在函数之外起作用。调用者在进行函数调用之前,必须事先申明被调用函数的
原型,包括返回值类型和参数类型。
CHAR*GetMemory(CHAR*p)
{
/*申请内存*/
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
P=(CHAR*)malloc(100);
Returnp;
}
Malloc申请的内存空间与操作系统有关,在PC中molloc申请空间以byte为单位,如申请
100个int内存则p=(INT*)malloc(400);
VOIDTest(void)
{
CHAR*str=NULL;
If(NULL!=GetMemory(&str))
{
Strcpy(str,”hellworld”);
Print(str);
Free(str);
Str=NULL;
Return;
}
5.返回值
C语言中,函数的调用者通过返回值了解函数的执行情况,函数缺省的返回值类型为int,
编程规范要求必须显示定义函数的返回类型。对于反映了函数执行状态的返回值,调用者必
须依据返回值进行相应的处理,尤其是对于函数执行异常的情形。函数的出口参数能够起到
与返回值类似的作用,上一条同样适用于出口参数。
对于函数返回值为恒值得函数,建议使用void返回值
#include“stdio.h”
Voidmain()
{
Char*p;
P=(char*)malloc(100);
If(p!=NULL)
{
Strcp(p,”helloworld!n”);
Printf(p);
Free(p);
}
Return;
}
LongA()
{
If(exp())
{
ReturnVOS_ERROR;
}
RuturnVOS_OK;
}
LongB()
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
{
If(A())
{
Dosomething1();
}
el
{
Dosomething2();
}
Return;
}
6.强制类型转换
强制类型转换给C编程带来了极大灵活性,也正是这种灵活性,埋下了无数隐患。
当目地结构的空间大于源结构的空间时,要重点关注内存访问超过源结构范围的情况,可能
越界。当目地结构的空间小于源结构空间时,要重点关注对目地结构赋值不能完全覆盖源结
构范围的情形,能遗漏。
与结构体之间的强制类型转换相比,基本数据的强制类型转换更容易出现上述情况
1)目地结构小于源结构
VoidB(char*p)
{
*p=1;
Return;
}
VoidA()
{
Ulonga;
B((char*)(&a));
Return;
}
A=?是1吗?答案:不可预知
2)目的结构大于源结构
VoidB(ulong*p)
{
*p=1000;
Return;
}
VoidA()
{
Uchara=10;
B((ulong*)(&a));
Return;
}
在函数B给*P赋值前,*P值时多少?答案:不可预知
*P赋值后,会出现什么情况?答案:越界
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
ca
C语言使用switchca处理一个条件的多个取值有不同的处理分支的情况。
当所有的ca都匹配不成功时,进入default分支。如果程序从逻辑上不可能走到这个分
支,可以在该分支中使用断言。
结束ca分支的执行最常用的办法是使用break/return,否则程序将自动进入下一个ca
分支继续执行。编译器对switch…..ca……可以做优化,用空间换取时间,default分支
按照编程规范,要求放在switchca的末尾,C本身不做强制要求。
Voidmain()
{
Longulcnt1=0,ulcnt2=0;
Char*ch=“aha!”;
While(*ch)
{
Switch(*ch)
{
Ca‘a’:
Ca‘h’:
ulcnt2++;
Default:
Ulcnt1++;
}
Ch++;
}
Printf(“%u,%un”,ulcnt1,ulcnt2);
Return;
}
Ulcnt1和ulcnt2分别是多少?Ulcnt1=4,ulcnt2=3
8.字符串
Ulongbuildrun((char**ppbuildrun)
{
Ulongullen;
Char*pbuf;
Ullen=calculatebuildrunlen();
If(0==ulen)
{
*ppbuildrun=NULL;
ReturnB_ZERO_LENGTH;
}
Pbuf=VOS_malloc(0,ullen);
If(NULL==pbuf)
{
*ppbuildrun=NULL;
ReturnB_MALLOC_FAILED;
}
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
VOS_Strcpy(pbuf,buildruninfo);
*ppbuildrun=pbuf;
ReturnVOS_OK;
}
案例点评:
为信息输出,字符串必不可少,字符串在动态申请时少分配一个字符是非常普遍的一个错误,
strlen等计算字符串长度的函数都是不考虑字符串的0结束符的,代码review时,字符串
越界问题是一个大客户,要盯紧看严
Sizeof与strlen的区别
Charchar[]=“abc”;sizeof(char)=4,strlen(char)=3
Charchar[]=“ab0c”;sizeof(char)=5,strlen(char)=2
Sizeof()为编译时执行,strlen()为运行时执行
Longgetxyhead(char**pdata,char**pbuf)
{
Ulongulen;
Char*ptmpdata=*pdata,*ptmbuf=*pbuf;
Ullen=analysisheand(ptmpbuf);
//strcpy(ptmpdata,ptmpbuf);
//sprintf(ptmpdata,ptmpbuf,ullen);
//memcpy(ptmpdata,ptmpbuf,ullen);
Returnullen;
}
*pbuf中存放的是xyz协议的peer发送过来的一段报文,这个函数负责将协议头拷贝到
pdata指向的空间中,假定空间是够,那条语句最合适?Mencpy语句最合适
因为没人保证*pbuf中不出现’0’,infact,协议头中非常容易出现’0’,此时它不
再是字符串,字符串工具函数必须是专款专用,而mencpy则要宽泛得多。
VoidgetdigitString(char*pdata,char*pbuf)
{
Char*ptmpdata=pdata,*ptmpbuf=pbuf;
While(‘0’!=(*ptmpbuf))
{
If((‘0’<=*ptmpbuf)&&(‘9’>=*ptmpbuf))
{
*ptmpdata=*ptmpbuf;
Ptmpdata++;
}
El
{
Break;
}
Ptmpbuf++;
}
*ptmpdata=’0’;/*没有’0’,就不是字符串*/
Return;
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
}
该函数功能是将pbuf中的连续数字拷贝到pdata中,生成一个新德字符串!
#defineBUFFER_SIZE250
VoidTest(void)
{
Charpszbuf[BUFFER_SIZE]=”0”;
Snprintf(pszbuf,sizeof(pszbuf)-1,”file:%sline:%s”,_FILE_,_LINE_);
Pszbuf[sizeof(pszbuf)-1]=‘0’;
Printf(“%s”,pszbuf);
Return;
}
#defineBUFFER_SIZE250
VoidTest(char*pszmsg)
{
Char*pszbuf=NULL;
If(NULL==pszmsg)
Return;
Pszbuf=malloc(BUFFER_SIZE+1);
If(pszbuf!=NULL)
{
Strncpy(pszbuf,pszmsg,BUFFER_SIZE);
Pszbuf[BUFFER_SIZE]=’0’;
Free(pszbuf);
}
Return;
}
案例点评
C语言提供的函数库字符串函数sprintf/vsprintf/strcpy/strcat/gets等非常危
险,很容易导致内存越界,应使用安全的字符串库函数snprintf/strncpy/strncat/
fgets指定操作内存大小。Strncpy等安全函数,当拷贝字符串到达指定的长度时,不会在
目标字符串结尾添加’0’,必须手工添加’0’,可以在调用strncpy后紧接着赋0,也
可以在申请内存时将最后一字节置0。
可以使用Dopra提供的改进的安全函数VOS_strncpy自动在目标字符串结尾添加’0’,但
要注意,此时拷贝的字符串字节数比标准C库的strncpy少了一个字节。
注意Dopra提供的VOS_strncpy的实现与标准C库一致,不会自动添加’0’。
函数功能:把190个字节空间中能容纳的非负整数按小到大的顺序不间断打印出来。
#defineBUFFER_SIZE190
Voidmain(void)
{
Charszbuf[BUFFER_SIZE]={0};
Inti=0;
For(;i<100;i++)/*190个字节空间最多能容纳多少个整数?100?*/
{
Sprintf(szbuf+strlen(szbuf),“%d”,i);
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
}
Printf(“%sn”,szbuf);
Return;
}
当i>=10时,每个整数占用两字节,循环100次后,Buffer总长度(包括’0’)会超
过190,导致内存写越界。
案例点评
上述例子属于人工计算字符串长度出错的典型例子,案例中100就是人工计算的魔鬼数字。
魔鬼数字是指代码中出现的难以理解的数字。这里的代码不仅仅指*.c文件,也包含宏定义。
简单的使用宏替换魔鬼数字并没用消失,正确的做法,除使用宏表明数字的含义外,还应从
若干基础数字自动运算出衍生数字,任何情况下都不要手工计算数字!计算数字的工作应由
计算机完成。
#defineBUFFER_SIZE190
Voidmain(void)
{
Charszbuf[BUFFER_SIZE]={0};
inti=0;
intiposition=0;/*记录每次copy的起始位置*/
intlength=0;/*记录每次copy的字符数(不算’0’)*/
for(;;i++)
{
Length=snprintf(snbuf+iposition,(BUFFER_SIZE-1)-iposition,%d”,i);
If(length<=0)/*如果length<=0,说明到达了缓冲区末尾,跳出
循环!*/
{
Szbuf[iposition]=‘0’;
Break;
}
Iposition+=length;
}
Printf("%sn”,szbuf);
Return;
}
9.资源释放
Voidprintdigit(ulonguldata)
{
Char*pbuf;
Pbuf=(char*)malloc(16);
If(NULL==pbuf)
{
ReturnNULL;
}
VOS_sprintf(pbuf,“%lunr”,uldata);
Printf(pbuf);
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
Free(pbuf);/*review九句箴言;看到malloc找free!*/
Return;
}
Node_head_s*createnode(ulongulntype)
{
Node_head_s*pnode;
Node_body_s*pbody;
Pnode=(Node_head_s*)malloc(sizeof(Node_head_s));
If(pnode==NULL)
{
ReturnNULL;
}
pbody=(Node_body_s*)malloc(sizeof(Node_body_s));
If(pbody==NULL)
{
Free(pnode);/*异常分支最容易忘记打扫战场,顾头也要顾尾*/
ReturnNULL;
}
Pnode->pbody=pbody;
Pnode->ulntype=ulntype;
Returnpnode;
}
案例点评
资源泄露是代码review中最常见的错误之一,申请的每个资源必须明确由谁负责释放,何
时何处释放。
在异常分支中,保持头脑清醒,清理战场。
在特定功能去使能时,需要完成的主要工作就是资源清退。
规范
Longlszero(ulongulcnt)
{
If(ulcnt==0)
{
ReturnW_ZERO;
}
El
{
ReturnW_NONZERO;
}
}
函数功能:参数为0返回W_ZERO,否则返回W_NONZERO
编程规范反复强调变量存放在==右边,常量放在左边,是为了规避出现if(ulcnt=0)
这种语法正确,但极有可能是笔误的情况。
If(ulcnt=uldata)if(ulcnt==uldata)ulcnt=
uldata;
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
{VS.{(ulcnt)
}}{
}
上面三种语句语法都正确,那种最好。第一种和第二种非常容易出现笔误,为了杜绝不不要
的问题,不要使用第一种,二用第三种代替。
VoidTest()
{
If(Func_A()&&Func_B())
{
Dosomething_A();
}
El
{
Dosomething_B();
}
Return;
}
上面程序在任何情况下是否语义都是正确的?
1)voidTest()
{
Ulongulret1,uiret2;
Ulret1=Func_A();
Ulret2=Fucn_B();
If(ulret1&&ulret2)
{
Dosomething_A();
}
El
{
Dosomething_B();
}
Return;
}
2)
VoidTest()
{
If(Func_A())
{
If(Func_B())
{
Dosomething_A();
}
El
{
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
Dosomething_B();
}
}
El
{
Dosomething_B();
}
Return;
}
如果程序逻辑为Func_A()的返回值为假的情况下不执行Func_B(),则上页程序是正确的,
但要求用2)的程序模式来实现。
如果Func_A和Func_B都无条件执行,再根据综合结果决定走哪个分支,则使用程序1)。
为了明确程序逻辑,同时要求注释加以说明,以便后期维护。
VOS_Asrt(Func_A())_DBGASSERT(Func_A())这两个用法有什么区别?
VOS_Asrt用法保证任何情况下Func_A都得到执行,VOS_DBGASSERT则与系统是打开
relea宏还是debug宏有关。当系统打开relea宏,则VOS_DBGASSERT在编译时将被忽
略,Func_A()得不到执行。使用VOS_DBGASSERT时要谨慎,要清楚了解不同系统宏的不同语
义,要保证两种场景下,程序逻辑都是正确的。
为了避免不必要的麻烦,要求禁止在VOS_DBGASSERT中使用函数。
LonglsA(charch)
{
If((‘a’!=ch)||(‘A’!=ch))/*逻辑恒为真,逻辑错误?笔误?*/
{
ReturnVOS_ERROR;
}
El
{
ReturnVOS_OK;
}
}
函数功能:判断输入字符是否是‘a’或‘A’,如果是,则返回VOS_OK。否则返回VOS_ERROR。
PC-LINT可以检查出逻辑恒为真或假的逻辑表达式基本都是我们预期之外的逻辑。
11.临界资源保护
Voidndmbuf(mbuf_s*pmbuf)
{
Intr_lock();/*彻底保护*/
空闲BD数=DRV_GetFreeBD();
报文片数=mbuf_GetFrag(pmbuf);
If(报文片数>空闲BD数)
{
Intr_unlock();/*打扫战场*/
Return(QOS_CONGEST);/*发送拥塞,暂时缓存*/
}
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
CopymbufintoBD();
空闲BD数-=报文片数;
Intr_lock();
}
Tasksock:Taskintr:
{{
……..…….
Sendmbuf(pmbuf);ndmbuf(pmbuf);
…….……..
}}
案例点评
这是一个经典的中断与任务直接临界资源缺乏保护的案例。中断也任务之间的临界资源保护
通常采用在任务中关闭中断的方式进行。关闭中断之后,对临界数据进行操作,操作完成之
后再开发中断。
该案例说明对临界资源的读操作也是需要进行保护,如果读得结果是一个判断的输入条件的
话。
Voidshow(SLIST_S*pcfgctionList)
{
SLIST_S*pSectionList=pcfgctionList,*pNext;
While(pSectionList)
{
pNext=pSection->pNext;
lHandle=tWaitlist(pNext);
EXEC_cutstring(0,pSectionList->pListName);/*有任务切换*/
pSectonList=pNext;
}
}
VoidFreeSection(SLIST_S*pcfgctionList)
{
SLIST_S*pSectionList=pcfgctionList,*pNext;
While(pSectionList)
{
pNext=pSectionList->pNext;
DeleteWaitList(pSectionList);
Free(pSectionList);
pSectionList=pNext;
}
Return;
}
案例点评
这是一个经典的任务之间临界资源缺乏保护的案例。在任务非抢占模型中,任务之间的临界
资源保护问题一定出现在任务切换的时候。任务切换之后,工作变量指向的内容可能发生变
化,这个时候需要进行保护解决任务之间临界资源报文的一个常用办法是使用WaitList。
产生任务切换的操作主要包括:同步IPC,RPC(包括同步,异步两种),信号量同步读,队
如果您需要使用本文档,请点击下载按钮下载!
授课:XXX
列同步读,事件同步读。VOS_T_Delay以及其他包含了这些API的函数调用。
(注:可编辑下载,若有不当之处,请指正,谢谢!)
本文发布于:2022-11-24 09:29:29,感谢您对本站的认可!
本文链接:http://www.wtabcd.cn/fanwen/fan/90/10892.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
留言与评论(共有 0 条评论) |