编程规则
函数变量定义
数据的传递尽量通过函数的参数来实现,函数前面的函数返回值类型最好是表示函数执行的状态。
比如:我们通过串口接收一个字节数据,我们可以定义函数如下:
BOOL
xSmsPortSerialGetByte(CHAR*pucByte)
{
*pucByte=UDR;
return TRUE;
}
还可以定义如下:
Char xSmsPortSerialGetByte(void)
{
return UDR;
}
第一种方法,调用函数后需要传递出一个接收到的数据,这个是通过函数的参数来传递的。第二种方法,是通过函数的返回值来传递的。
编译
代码编译后不能存在warning。
以后一定注意不能忽略编译器的警告
发表于2008-7-1322:54:25
1,为什么容易忽略编译器的警告信息?
1)编程时处理各种error已经很让人恼火的了,error自然被放在次要位置;
2)绝大多数下,程序在存在warnings的情况下,在短期内可以正常运行,我们根本看不到错误,导致warning近一步被轻视;
3)没有养成好的工作习惯,带有warning的代码是不能交付的,而我们判断交付的标准中往往没有对代码提出更为细致的要求。
2,忽视编译警告往往会给我们造成重大损失
最近的一个项目中,我的程序在实验室测试了1个星期没有出现问题。当设备部署到现场3天后,坏事情终于发生了:设备依次出现死机问题。
再从头分析代码,百思不得其解,痛不欲生的时候注意到了一个编译警告,说一个表达式一直为真。原来发生了下面的错误:
unsigned char i;
for(i=0;i<1000;i++){.....}
变量类型的定义导致了表达式一直为真。为什么开始的测试中不会出现问题哪?原来,这段代码是必须程序运行一段时间后才会执行的一段代码,在短时间的测试中根本不会进入。
看来编程真不能偷懒,偷懒必遭惩罚,你还要费比当时偷懒省的劲多好多倍的劲来弥补,并且这个弥补的过程中你还要承受巨大的压力。跟平时多流汗战时少流血的道理一样的。
3)解决办法
给自己的编程增加一个规则,带有编译警告的代码不能交付。
引用没有声明的外部函数可能会导致严重问题
using a function without a valid prototype is VERY dangeours.
在一个函数中调用另外一个文件中定义的函数,但是这个函数没有进行声明,会出什么问题哪?
大多数的情况下可能会出现问题,有些情况下,可能是幸运不会出现问题。
看下面这段代码:
unsigned long ulWater_Max_Value=0xABCDEFUL;
Puthexbyte(ulWater_Max_Value>>24);
Puthexbyte(ulWater_Max_Value>>16);
Puthexbyte(ulWater_Max_Value>>8);
Puthexbyte(ulWater_Max_Value);
其中Puthexbyte函数在另外一个c文件中进行了定义,但是在该文件对应的. 件中,我没有对这个函数进行声明。
这样,当运行上面的代码的时候,会导致错误的结果,输出的数据全部为0。
改正的方法是,为每个外部函数在.件做一个相应的声明。每个需要调用这个函数的文件都要包含这个.h头文件。
仍然是上面的代码,如果把变量定义为unsigned int形式,却可以输出正确结果。这有点奇怪,绝对是侥幸,但是,为什么会对哪?即:
unsigned int ulWater_Max_Value=0xABCD;
//Puthexbyte(ulWater_Max_Value>>24);
//Puthexbyte(ulWater_Max_Value>>16);
Puthexbyte(ulWater_Max_Value>>8);
Puthexbyte(ulWater_Max_Value);
在不声明Puthexbyte函数的情况下,仍然能正确输出,为什么哪?
assert(断言)的使用
编辑:程序设计发表日期:2007-05-1321:56
原创作者:Qdieyou,转载请加注。
程序一般分为Debug版本和Release版本,Debug版本用于内部调试,Release版本发行给用户使用。(这个概念可能不大好理解,想想VC下,调试时有个选项,一个是debug,一个是release)
assert(表达式);的意思是:当表达式为真时,程序继续运行,如果表达市为假,那程序就会停止运行,并提示错误信息。
注意:assert是一个宏,只在debug版本中起作用,在release版本中,该语句是不起任何作用的。
先简单的看一个例子吧!
以下为一个使用了断言的C源程序:
#include<stdio.h>
#include<assert.h>
void test(int*p)
{
assert(p!=NULL);
printf("%d\n",*p);
}
int main(void)
{
test(NULL);
}
编译及运行结果:
Qdieyou@qdieyou/cygdrive/e/gcc
$gcc-o assert assert.c
Qdieyou@qdieyou/cygdrive/e/gcc
$./assert
assertion"p!=NULL"failed:file"assert.c",line6
19331[sig]assert3288e:\:***fatal error-called with threa
dlist_ix-1
Hangup
程序说明:由于我们在main函数中传了NULL指针值给test函数,在test函数执行到assert(p!=NULL);发现表达式不为真,就终止了程序的运行,并提示错误的行数信息。
注意:由于assert宏只在debug版本中起作用,所以assert一般只用于内部函数对参数有效性进行检查,如果该函数作为一个外部接口来使用时,一般需要利用if,else语句进行防错设计。——Qdieyou
以下摘自《C高效编程》
【规则6-5-1】使用断言捕捉不应该发生的非法情况。不要混淆非法情况与错误情况之间的区别,后者是必然存在的并且是一定要作出处理的。
【规则6-5-2】在函数的入口处,使用断言检查参数的有效性(合法性)。
【建议6-5-1】在编写函数时,要进行反复的考查,并且自问:“我打算做哪些假定?”一旦确定了的假定,就要使用断言对假定进行检查。
【建议6-5-2】一般教科书都鼓励程序员们进行防错设计,但要记住这种编程风格可能会隐瞒错误。当进行防错设计时,如果“不可能发生”的事情的确发生了,则要使用断言进行报警。
编程的时候,看到下面的一个函数:
eMBErrorCode
eMBASCIIReceive(UCHAR*pucRcvAddress,UCHAR**pucFrame,USHORT*pusLength) {
eMBErrorCode eStatus=MB_ENOERR;
ENTER_CRITICAL_SECTION();
assert(usRcvBufferPos<MB_SER_PDU_SIZE_MAX);
/*Length and CRC check*/
if((usRcvBufferPos>=MB_SER_PDU_SIZE_MIN)
怎么用printf输出bool函数值&&(prvucMBLRC((UCHAR*)ucASCIIBuf,usRcvBufferPos)==0))
{
/*Save the address field.All frames are passed to the upper layed
*and the decision if a frame is used is done there.
*/
*pucRcvAddress=ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
/
*Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
*size of address field and CRC checksum.
*/
*pusLength=(USHORT)(usRcvBufferPos-MB_SER_PDU_PDU_OFF-MB_SER_PDU_SIZE_LRC);
/*Return the start of the Modbus PDU to the caller.*/
*pucFrame=(UCHAR*)&ucASCIIBuf[MB_SER_PDU_PDU_OFF];
}
else
{
eStatus=MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
当看到UCHAR**pucFrame的时候就优点晕了。不就是传出一个地址吗?干吗还要用指向指针的指针?于是,顺手修改了程序,将那个形参变成了UCHAR*pucFrame,具体的函数也变成了下面的样子:
eMBErrorCode
eMBASCIIReceive(UCHAR*pucRcvAddress,UCHAR*pucFrame,USHORT*pusLength)
{
eMBErrorCode eStatus=MB_ENOERR;
ENTER_CRITICAL_SECTION();
assert(usRcvBufferPos<MB_SER_PDU_SIZE_MAX);
/*Length and CRC check*/
if((usRcvBufferPos>=MB_SER_PDU_SIZE_MIN)
&&(prvucMBLRC((UCHAR*)ucASCIIBuf,usRcvBufferPos)==0))
{
/*Save the address field.All frames are passed to the upper layed
*and the decision if a frame is used is done there.
*/
*pucRcvAddress=ucASCIIBuf[MB_SER_PDU_ADDR_OFF];
/*Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
*size of address field and CRC checksum.
*/
*pusLength=(USHORT)(usRcvBufferPos-MB_SER_PDU_PDU_OFF-MB_SER_PDU_SIZE_LRC);
/
*Return the start of the Modbus PDU to the caller.*/
pucFrame=(UCHAR*)&ucASCIIBuf[MB_SER_PDU_PDU_OFF];
}
else
{
eStatus=MB_EIO;
}
EXIT_CRITICAL_SECTION();
return eStatus;
}
编译后运行,却得不到自己想要的结果。只得回来再分析这个函数。首先搜索到CSDN 上的一篇文章;
dev.csdn/author/kgdiwss/477390ddb16b44faa06b6c7014908469.html,里面又好好地对指针做了分析。我下面就把自己的理解再写一遍,以加深印象。
1,指针在编译的时候会变成什么?
如果我们如下声明变量:
char a;
short int i;
short int*pi;
那么编译的时候,编译器会在内存空间的某处为上面的变量开辟存储空间,下面是一个可能的示意图:
内存地址12345678910
------------------------------
|char a|short int i|short int*pi|
如上所示,int a占用一个字节,short int i占用2个字节,short int*pi占用2个字节。当我们做了如下的赋值的时候:
i=88;
pi=&a;
内存映象会得到改变: