C语言编程基础培训教材

发布时间:2021-03-30   来源:文档文库   
字号:
希望对大家有所帮助,多谢您的浏览!
目录
1.简单就是美 2. sizeof 3.字节序 4.函数参数 5.返回值
6.强制类型转换 7.swith case 8.字符串 9. 资源释放 10. if规范
11. 临界资源保护

1.简单就是美
优先级搞不清楚用括号
复合语句太罗嗦,拆成几行来写 编码的三不原则

不要挑战自己的记性 不要挑战自己的耐心 不要挑战编译器的水平 编码的三用原则
能用简单句的,就不要用复杂的技巧 能用成熟代码的,就不要再来一套 能用上工具的,就一定要机械化 Struct xxx {
char cA; short sB; long lC; }

void main( {
char *pchar;
pchar = (char *malloc(7; /* 1—魔鬼的数字;2—申请失败后怎么办? */ mencpy (pchar, abcdefgh, sizefo(xxx; /* 3—内存 */ printf(%s\n, pchar; /* 缺少字符串结束符必越界 */ return; /* 5—退出前没有释放内存 */ }
2. sizeof
数据结构是C语言的基础。C语言的灵活性很大,程度上在于其数据结构的灵活性。要用好的数据结构,首先要掌握数据结构的大小的计算,系统的每个数据机构,每个变量都会分配到一个对应的存储空间,这个存储空间的大小就是数据结构的尺寸。 sizeof 为编译时的一元运算符,可用来计算任一对象的大小1 / 13
希望对大家有所帮助,多谢您的浏览!
sizeof 的结果是编译时的常量
sizeof 不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型。如未知存储大小的数组类型,未知内容的结构或联合类型,void 类型等。 3. 字节序 X86系统
void QosConfigPolicy(xxx {
ulDestIP = 从命令行读取用户配置的参数; pQosPoliscy->ulDestIP = ulDestIP; return; }
主机处理
void QosClassify(xxx {
Plp = (IP-S*pData;
If(pQosPolicy->ulDestIP == plp->ulDestIP {
Vos_HTONL(pQosPolicy->ulDestIP; DoSomething(; }
Return; }
由于历史的原因,世界存在两种字节序标准——BigEndianLittleEndianPower PC大头,X86是小头。有些CPU可以通过寄存器设置支持不同的字节序。如MIPS BigEndian——高位在低字节,地位在高字节 LittleEndian——低位在低字节,高位在高字节
eg0x345678 大头内存从低到高存放次序 00,34,56,78;小头内存从低到高存放次序 78,56,34,00
字节序问题广泛存在于设备与设备之间,单板与单板之间,单板与底层之间,只要两个处理单元的字节序不同,这个问题就存在。为了解决不同字节序的处理单元之间的同信问题,界上定义了主机序和网络序的概念,网络序主要用于信息传递,一般不用于计算,其字节顺序与大头一致。
除在编码时紧绷这根弦之外,我们在器件选择是主机序与网络序一致的芯片,同一设备的不同单板使用相同的字节序。并优先选择支持大头的芯片,这样即使不能彻底解决问题,也可以彻底规避问题。 4. 函数参数
C语言中,函数通过返回值和参数与调用者交换信息。函数参数自身占用的存储单元在堆栈中分配。入口参数指向的数组或地址,在函数入口处拷贝到堆栈区中,因此对函数参数所在存储单元的直接修改不会作用到函数之外,而对参数存储单元中存放的地址指向的存储空间的修改,则会在函数之外起作用。调用者在进行函数调用之前,必须事先申明被调用函数的原型,包括返回值类型和参数类型。 CHAR *GetMemory(CHAR *p {
/* 申请内存 */ 2 / 13
希望对大家有所帮助,多谢您的浏览!
P = (CHAR *malloc(100; Return p;
}
Malloc 申请的内存空间与操作系统有关,PCmolloc申请空间以byte为单位,如申请100int内存则 p=(INT *malloc(400; VOID Test(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 Void main( {
Char *p;
P = (char *malloc(100; If(p != NULL {
Strcp(p,hello world!\n; Printf(p; Free(p; }
Return; }
Long A( {
If (exp( {
Return VOS_ERROR; }
Ruturn VOS_OK; }
Long B(3 / 13
希望对大家有所帮助,多谢您的浏览!
{
If (A( {
Dosomething1(; } else {
Dosomething2(; }
Return; }
6.强制类型转换
强制类型转换给C编程带来了极大灵活性,也正是这种灵活性,埋下了无数隐患。 当目地结构的空间大于源结构的空间时,要重点关注内存访问超过源结构范围的情况,可能越界。当目地结构的空间小于源结构空间时,要重点关注对目地结构赋值不能完全覆盖源结构范围的情形,能遗漏。
与结构体之间的强制类型转换相比,基本数据的强制类型转换更容易出现上述情况 1 目地结构小于源结构 Void B (char *p {
*p = 1; Return; }
Void A( {
Ulong a;
B((char*(&a; Return; }
A = ? 1吗? 答案: 不可预知 2 目的结构大于源结构 Void B(ulong *p {
*p = 1000; Return; }
Void A( {
Uchar a = 10; B((ulong *(&a; Return; }
在函数B*P赋值前,*P值时多少? 答案:不可预知 *P赋值后,会出现什么情况? 答案:越界4 / 13
希望对大家有所帮助,多谢您的浏览!
7. switch case
C语言使用switch case处理一个条件的多个取值有不同的处理分支的情况。
当所有的case都匹配不成功时,进入default分支。如果程序从逻辑上不可能走到这个分支,可以在该分支中使用断言。
结束case分支的执行最常用的办法是使用break/return否则程序将自动进入下一个case分支继续执行。编译器对switch..case……可以做优化,用空间换取时间,default分支按照编程规范,要求放在switch case的末尾,C本身不做强制要求。 Void main(
Long ulcnt1=0,ulcnt2=0; Char *ch = aha!; While(*ch {
Switch (*ch {
Case a: Case h:
ulcnt2++;
Default:
Ulcnt1++; } Ch++; }
Printf (%u, %u\n,ulcnt1,ulcnt2; Return; }
Ulcnt1ulcnt2分别是多少? Ulcnt1=4,ulcnt2=3 8.字符串
Ulong buildrun ((char **ppbuildrun {
Ulong ullen; Char *pbuf;
Ullen = calculatebuildrunlen(; If ( 0 == ulen {
*ppbuildrun = NULL; Return B_ZERO_LENGTH; }
Pbuf = VOS_malloc ( 0,ullen; If (NULL == pbuf {
*ppbuildrun = NULL; Return B_MALLOC_FAILED;
}5 / 13
希望对大家有所帮助,多谢您的浏览!
VOS_Strcpy(pbuf, buildruninfo; *ppbuildrun = pbuf; Return VOS_OK; }
案例点评: 为信息输出,字符串必不可少,字符串在动态申请时少分配一个字符是非常普遍的一个错误,strlen等计算字符串长度的函数都是不考虑字符串的\0结束符的,代码review时,字符串越界问题是一个大客户,要盯紧看严 Sizeofstrlen的区别
Char char[ ] = abc; sizeof (char = 4, strlen(char = 3 Char char[ ] = ab\0c; sizeof (char = 5, strlen(char = 2 Sizeof( 为编译时执行,strlen(为运行时执行 Long getxyhead ( char **pdata, char ** pbuf {
Ulong ulen;
Char *ptmpdata = *pdata, *ptmbuf = *pbuf; Ullen = analysisheand(ptmpbuf; // strcpy(ptmpdata, ptmpbuf;
// sprintf(ptmpdata, ptmpbuf, ullen; // memcpy(ptmpdata, ptmpbuf, ullen; Return ullen; }
*pbuf 中存放的是xyz协议的peer发送过来的一段报文,这个函数负责将协议头拷贝到pdata指向的空间中,假定空间是够,那条语句最合适? Mencpy语句最合适
因为没人保证 *pbuf中不出现’\0’,infact,协议头中非常容易出现’\0’,此时它不再是字符串,字符串工具函数必须是专款专用,而 mencpy则要宽泛得多。 Void getdigitString (char *pdata, char *pbuf {
Char * ptmpdata = pdata, *ptmpbuf = pbuf; While ( \0 != (*ptmpbuf {
If ((0 <= *ptmpbuf && (9 >= *ptmpbuf {
*ptmpdata = *ptmpbuf; Ptmpdata++; } Else {
Break; }
Ptmpbuf++; }
*ptmpdata=\0; /* 没有’\0’,就不是字符串 */
Return ;6 / 13
希望对大家有所帮助,多谢您的浏览!
}
该函数功能是将pbuf中的连续数字拷贝到pdata中,生成一个新德字符串! #define BUFFER_SIZE 250 Void Test ( void {
Char pszbuf[BUFFER_SIZE]=\0;
Snprintf( pszbuf, sizeof(pszbuf-1,file:%s line: %s, _FILE_, _LINE_; Pszbuf[sizeof(pszbuf-1] = \0; Printf(%s, pszbuf; Return; }
#define BUFFER_SIZE 250 Void Test ( 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个字节空间中能容纳的非负整数按 小到大的顺序不间断打印出来。 #define BUFFER_SIZE 190 Void main (void {
Char szbuf[ BUFFER_SIZE ] ={ 0}; Int i = 0;
For ( ; i < 100; i++ /* 190个字节空间最多能容纳多少个整数?100*/ {
Sprintf( szbuf + strlen(szbuf, %d,i;7 / 13
希望对大家有所帮助,多谢您的浏览!
}
Printf(%s\n, szbuf; Return ; }
i >= 10 时,每个整数占用两字节,循环100次后,Buffer总长度(包括’\0’)会超190,导致内存写越界。 案例点评
上述例子属于人工计算字符串长度出错的典型例子,案例中100就是人工计算的魔鬼数字。魔鬼数字是指代码中出现的难以理解的数字。这里的代码不仅仅指*.c文件,也包含宏定义。简单的使用宏替换魔鬼数字并没用消失,正确的做法,除使用宏表明数字的含义外,还应从若干基础数字自动运算出衍生数字,任何情况下都不要手工计算数字!计算数字的工作应由计算机完成。
#define BUFFER_SIZE 190 Void main (void {
Char szbuf[BUFFER_SIZE] = {0}; int i = 0;
int iposition = 0; /*记录每次copy的起始位置 */
int length = 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 ("%s\n,szbuf; Return; }
9. 资源释放
Void printdigit ( ulong uldata {
Char *pbuf ;
Pbuf = (char *malloc(16; If (NULL == pbuf {
Return NULL; }
VOS_sprintf ( pbuf, %lu\n\r, uldata ;
Printf (pbuf ;8 / 13
希望对大家有所帮助,多谢您的浏览!
Free (pbuf ; /* review九句箴言;看到mallocfree */ Return ;
}
Node_head_s *createnode ( ulong ulntype {
Node_head_s * pnode; Node_body_s *pbody;
Pnode = (Node_head_s *malloc(sizeof(Node_head_s; If ( pnode == NULL {
Return NULL;
} pbody = (Node_body_s *malloc(sizeof(Node_body_s; If ( pbody == NULL {
Free (pnode ; /* 异常分支最容易忘记打扫战场,顾头也要顾尾 */ Return NULL; }
Pnode->pbody = pbody; Pnode->ulntype = ulntype; Return pnode; }
案例点评
资源泄露是代码review中最常见的错误之一,申请的每个资源必须明确由谁负责释放,何时何处释放。
在异常分支中,保持头脑清醒,清理战场。
在特定功能去使能时,需要完成的主要工作就是资源清退。 10. if 规范
Long lszero ( ulong ulcnt {
If ( ulcnt == 0 {
Return W_ZERO; } Else {
Return W_NONZERO; } }
函数功能:参数为0返回W_ZERO,否则返回W_NONZERO
编程规范反复强调变量存放在 ==右边,常量放在左边,是为了规避出现 ifulcnt = 0这种语法正确,但极有可能是笔误的情况。
If (ulcnt = uldata if(ulcnt == uldata ulcnt = uldata; 9 / 13
希望对大家有所帮助,多谢您的浏览!
{ VS. { VS. if ulcnt } } { }
上面三种语句语法都正确,那种最好。第一种和第二种非常容易出现笔误,为了杜绝不不要的问题,不要使用第一种,二用第三种代替。 Void Test ( {
If ( Func_A( && Func_B( {
Dosomething_A(; } Else {
Dosomething_B(; }
Return; }
上面程序在任何情况下是否语义都是正确的? 1 void Test(
{ Ulong ulret1, uiret2; Ulret1 = Func_A(; Ulret2 = Fucn_B(; If ( ulret1 && ulret2 { Dosomething_A(; } Else { Dosomething_B(; } Return; } 2
Void Test ( {
If (Func_A( {
If (Func_B( {
Dosomething_A(; } Else
{10 / 13
希望对大家有所帮助,多谢您的浏览!
Dosomething_B(; } } Else {
Dosomething_B(; }
Return; }
如果程序逻辑为Func_A(的返回值为假的情况下不执行Func_B(,则上页程序是正确的,但要求用2)的程序模式来实现。
如果Func_AFunc_B都无条件执行,再根据综合结果决定走哪个分支,则使用程序1 为了明确程序逻辑,同时要求注释加以说明,以便后期维护。
VOS_Assert(Func_A( VS. VOS_DBGASSERT(Func_A( 这两个用法有什么区别?
VOS_Assert用法保证任何情况下Func_A都得到执行,VOS_DBGASSERT则与系统是打开release宏还是debug宏有关。当系统打开release宏,则VOS_DBGASSERT在编译时将被忽略,Func_A(得不到执行。使用VOS_DBGASSERT时要谨慎,要清楚了解不同系统宏的不同语义,要保证两种场景下,程序逻辑都是正确的。
为了避免不必要的麻烦,要求禁止在VOS_DBGASSERT中使用函数。 Long lsA (char ch {
If ((a != ch || (A != ch /* 逻辑恒为真,逻辑错误?笔误? */ {
Return VOS_ERROR; } Else {
Return VOS_OK; } }
函数功能:判断输入字符是否是aA如果是,则返回VOS_OK否则返回VOS_ERROR PC-LINT可以检查出逻辑恒为真或假的逻辑表达式基本都是我们预期之外的逻辑。 11. 临界资源保护
Void sendmbuf ( mbuf_s *pmbuf {
Intr_lock(; /* 彻底保护 */ 空闲BD = DRV_GetFreeBD(; 报文片数 = mbuf_GetFrag(pmbuf; If (报文片数 > 空闲BD {
Intr_unlock(; /* 打扫战场 */
Return ( QOS_CONGEST; /* 发送拥塞,暂时缓存 */
}11 / 13
希望对大家有所帮助,多谢您的浏览!
CopymbufintoBD(;
空闲BD -= 报文片数; Intr_lock(; }
Task sock: Task intr: { {
…….. …….
Sendmbuf(pmbuf; sendmbuf(pmbuf; ……. …….. } } 案例点评
这是一个经典的中断与任务直接临界资源缺乏保护的案例。中断也任务之间的临界资源保护通常采用在任务中关闭中断的方式进行。关闭中断之后,对临界数据进行操作,操作完成之后再开发中断。
该案例说明对临界资源的读操作也是需要进行保护,如果读得结果是一个判断的输入条件的话。
Void show (SLIST_S *pcfgsectionList {
SLIST_S *pSectionList = pcfgsectionList , * pNext; While(pSectionList {
pNext = pSection->pNext;
lHandle = setWaitlist(pNext;
EXEC_cutstring( 0, pSectionList->pListName; /* 有任务切换 */ pSectonList = pNext; } }
Void FreeSection (SLIST_S *pcfgsectionList {
SLIST_S *pSectionList = pcfgsectionList , * pNext; While(pSectionList {
pNext = pSectionList->pNext; DeleteWaitList(pSectionList; Free(pSectionList; pSectionList = pNext; }
Return; }
案例点评
这是一个经典的任务之间临界资源缺乏保护的案例。在任务非抢占模型中,任务之间的临界资源保护问题一定出现在任务切换的时候。任务切换之后,工作变量指向的内容可能发生变化,这个时候需要进行保护解决任务之间临界资源报文的一个常用办法是使用WaitList产生任务切换的操作主要包括:同步IPCRPC(包括同步,异步两种),信号量同步读,队12 / 13
希望对大家有所帮助,多谢您的浏览!
列同步读,事件同步读。VOS_T_Delay以及其他包含了这些API的函数调用。


(注:可编辑下载,若有不当之处,请指正,谢谢!


13 / 13

本文来源:https://www.2haoxitong.net/k/doc/39e7024853e79b89680203d8ce2f0066f4336433.html

《C语言编程基础培训教材.doc》
将本文的Word文档下载到电脑,方便收藏和打印
推荐度:
点击下载文档

文档为doc格式