数据类型、数组越界和宏定义引发的悲剧

    小编在定义一个变量的时候,数据类型往往不会经过特别的考虑;在使用数组的时候,很多时候会忽略数组越界的问题,因为这个问题在C/C++编译器中是不进行检查的,越界不是编译、链接错误,运行时也未必会出错;但是直到上面的“不小心”引发问题的时候就会让人手足无措,当检查出问题的时候才追悔莫及。

    如果是在visual C++里进行编程,出现上述的情况估计不是大问题,因为C++的调试工具使用非常方便,可以相对容易地发现问题。但是,往往小编遇到的问题是在嵌入式系统中运行的,这个和硬件相关,且调试工具不是那么方便,出现问题的时候就很头大了。以下举几个我曾经遇到的例子。

    (1)我在大二的时候做一个智能车的比赛,单片机从传感器采集数据利用自带的AD转化成数字量,存入16位的寄存器。考虑到节约内存,于是小编想将结果存储到unsigned short (16位)中,这个本来是没有多大问题。但是,小编还希望多次采集数据取平均值,当时是3次。为了防止溢出,每次将采集到的数据先除以3,再进行相加。后来,我改成了采集6次数据,但是除数忘了改,还是3。于是,悲剧就发生了:智能车上有两组传感器,在AD值比较大的位置,得到的数据溢出,比较小的地方正常,这样,整个系统当然不能正常工作。还好,当时是用飞思卡尔的codewarrior进行调试,BDM可以无干扰的观察在线数据,及时发现了溢出的问题。后来改成long型变量,就没有问题了,也不用考虑那么多。现在想想,真是不值得,那个芯片的RAM还是挺大的,何必为了一点内存自找这么多麻烦?不过定义大数组的时候变量的类型和数组长度应该坚持“够用就好”。
    (2)第二个例子问题相当严重!!!我整整被这个问题困扰了两个月!现在忽然有点恍然大悟的感觉,于是才下定决心写这篇文章。小编的一个项目使用AT91作为单片机,其中应用了uC/OS操作系统,使用AT91的串口接收数据,中断方式,以g_Frame1作为接受缓冲,接受数据时将数据从g_Frame1拷贝到g_Frame,他们的类型都是UARTFRAME_DATA,数据类型定义为:
typedef struct
{
    Uint8   byHead;
    Uint8   byLength;
    Uint8   byData[150];
} UARTFRAME_DATA;
    程序运行通常没有问题,但是运行一段时间(通常是2小时或者更长时间,甚至长达4天才出现一次)之后会出现卡死的情况,且卡死得很诡异,主程序已经不能运行,但是中断却可以进入。于是我就认为是串口的问题,把串口的配置改来改去还是不行。后来认为是操作系统的问题,但是把串口接收程序去掉之后,操作系统运行很长时间也不会出现问题。再后来认为可能是中断和主程序同时访问一个变量出现问题,于是加入临界区,但是这个显然不会起什么作用,因为即使同时访问,应该只是数据错误,而不应该是卡死。在各种改啊改之后,每次都以为“啊,这次终于你妹的解决了这个问题!”的时候,总是过不了多久,就旧病复发了。每次都想说“去你妹的,你搞了我两个月了”的时候,又总是鼓起勇气再次调试。
    其实小编很早就发现了一个现象,就是如果发送的数据比较混乱,出现了错误,程序一两分钟就会卡死。但是没有把这个现象和问题出现的原因联系起来,终于一个偶然的机会……
    我以为是在处理接收到的报文(保存在g_Frame)的时候,由于第二个字节错误,于是产生了数组越界的情况,又因为使用了操作系统,所以在修改g_Frame的时候,把g_Frame以外的变量也修改了,其中可能有操作系统中的变量,于是导致操作系统崩溃。于是,我将g_Frame的数据长度加长到300,但是发现烧写程序失败了。于是小编开始使用我最惯用的伎俩——屏蔽法,把程序一段一段的注释掉,发现是TestSendUDP的问题,发现这个函数只能发送255个字符,多一个字符烧写就会失败,怪哉啊!——但是真相只有一个,并且已经离我不远了。让我们来看看TestSendUDP的定义,大家不要笑:
void TestSendUDP(Uint32 dstIp, Uint16 dstPort,  char* ch, Uint16 len)
{
char   i;
   ……
for(i=0;i<len;i++)
{
*(payload+i)=ch[i];
}
……
}

まさか!?就是那个char让我自己坑了自己两个月!i的大小最大只有127而已,如果传入的len太大,那么,函数中的for就陷入死循环了。如果传入的数据全都正确自然没有问题,但是串口传输数据很多时候不可靠,万一出错,且恰好是byLength,变得很大,这样不就卡在这个函数中了。i++最多可以加到-1(0xff),这时和Uint len=255相等,于是退出,故最多可以发送255个字符。此处,我们还可以看到,这个编译器对符号不敏感,只是比较两个数值的十六进制数。不过,如果要求i>len的时候可能效果会不一样(这个我没有试过)。这里之所以会陷入死循环,是因为我是这么调用的:TestSendUDP(0x808003b9,0x0a0a,(char*)&g_Frame,g_Frame.byHead-1+8);这样,长度就有可能超过255。

    那时候年轻,不懂事,以至于犯了这种错误。现在就知道定义变量类型的时候需谨慎。虽然改好之后还没有确定是这个问题,但是估计八九不离十,但如果不是这个问题,也算是长了见识了。
   (3)宏定义的问题
    有些时候百思不得其解,那段程序分明是执行了,但是为什么会没有应有的现象?变量分明定义了,为什么编译器告诉你没有定义?答案可能是:它没有执行,没有定义,只是你被骗了。
void USARTIni(UART_MODLE *UART_info)
{

    (对USART进行初始化)
}
    这个函数是执行了,并且使用SourceInSight可以看到定义:
#define USART               USART0
    但是当调用串口发送函数的时候,在UART0相应的引脚上没有任何波形,反而在UART1相应的引脚上有波形,这是为何?那是因为:
#if UART_PORT == UART0_PORT
#define USART               USART0
#else
#define USART               USART1
#endif
且有:
#define UART_PORT   UART1_PORT

    因此夹在宏定义之间的东西一定要仔细看。第二个情况类似。宏定义是预编译时候起作用的,相当于一些开关,告诉编译器是否编译某些内容,如何编译。




  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值