小编在定义一个变量的时候,数据类型往往不会经过特别的考虑;在使用数组的时候,很多时候会忽略数组越界的问题,因为这个问题在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

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

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

  1. STM32的C语言重点知识(1.C语言数据类型+2.C语言宏定义+3.C语言typedef+4.C语言结构体+5.C语言枚举)

    1.C语言数据类型: 注:目的是看到stdint,如看到int8_t;uint16_t能够瞬间知道表示的是char,8字节:unsigned short,16字节. ST关键字意思是在老版本的引脚说明 ...

  2. C语言数组使用、数组相关的宏定义剖析,及矩阵乘积、杨辉三角实例

         数组一直是编程语言学习中需要重点掌握的部分,它是最基本的数据结构类型,是构成字符串及其他许多重要构造结构的基础.相对许多其他高级语言来说,C语言对数组本身提供的支持并不太多,尤其是不支持动态 ...

  3. C语言-入门-宏定义(十七)

    预处理 编译一个C语言程序的第一步骤就是预处理阶段,这一阶段就是宏发挥作用的阶段.C预处理器在源代码编译之前对其进行一些文本性质的操作,主要任务包括删除注释.插入被#include进来的文件内容.定义 ...

  4. C++宏定义的优缺点

    一.#define的基本用法     #define是C语言中提供的宏定义命令,其主要目的是为程序员在编程时提供一定的方便,并能在一定程度上提高程序的运行效率,但学生在学习时往往不能 理解该命令的本质 ...

  5. c语言宏定义替换字符串,C语言中,宏替换的替换规则

    匿名用户 1级 2011-10-25 回答 简单来说:宏定义又称为宏代换.宏替换,简称"宏".是C提供的三种预处理功能的其中一种. 复杂的请看下面,讲的很全.下面的带参宏定义,多行 ...

  6. matlab 类似宏定义,比较全面的宏定义解析

    宏定义 宏定义是C提供的三种预处理功能的其中一种,这三种预处理包括:宏定义.文件包含.条件编译. 参数 不带参数 宏定义又称为宏代换.宏替换,简称"宏". 格式: #define ...

  7. C++中的宏定义详解

    转载自:C++中的宏定义 和 C++宏定义详解 目录 一.#define解析 1 #define命令剖析 1.1   #define的概念 1.2 宏替换发生的时机 1.3 ANSI标准说明了五个预定 ...

  8. C语言宏定义(宏参数创建字符串、预处理粘合剂)

    #define CNAME value   或者 #define CNAME expression,常见的常量的定义是这样的,但是宏定义可比想象中的要灵活的多.它可以定义宏常量.宏函数,还可以输入数据 ...

  9. 【C语言】高级宏定义

    前言 宏定义分为不带参数的宏定义和带参数的宏定义,不带参数的宏定义就是普通的宏定义,带参数的宏定义则稍稍复杂.下面将结合一些例子讲解这些显得比较高级的宏定义. 文章目录 前言 一.高级宏定义 1.#d ...

最新文章

  1. 一说“并发”就想到“多线程”,那就局限了
  2. ubuntu 10.04 常用 设置
  3. 【编程】堆(heap)和栈(stack)的区别
  4. NTLDR is missing解决方法
  5. nginx系列之三:日志配置
  6. VS Code 变身约会利器!以码会友,轻松找到心仪的TA!
  7. Requests库实战(一)---网页采集器
  8. python命令行调试django代码_Django shell调试models输出的SQL语句方法
  9. app与后台交互之间的几种安全认证机制
  10. MyBatis返回结果不稳定
  11. 邮件中html格式转换,如何在宏中将邮件格式更改为html?
  12. salt 安装MySQL-python和过程
  13. 计算机无法进行磁盘,电脑硬盘无法分区怎么办
  14. 海康威视摄像头rtsp推流至H5总结
  15. aw36515闪光灯驱动ic调试
  16. Apache Jena TDB 常用API
  17. Nat Commun:中国中医科学院黄璐琦院士/首都医科大学高伟教授团队联合解析雷公藤甲素生物合成关键C-14位羟化机制...
  18. TCP的三次握手和四次挥手
  19. 报税系统代理服务器地址6,报税系统服务器地址怎么填
  20. QQ空间相册脸部识别

热门文章

  1. ACM简单题——不能被3整除的数
  2. 【Qt5】创建文件夹
  3. 昨天的双十一你又剁手了吗?
  4. 自然语言处理某个pipeline
  5. 【代码1】应用眼中的操作系统;系统调用
  6. 华为的网络模拟器eNSP
  7. JVM--GC相关记录
  8. iOS音视频实现边下载边播放
  9. T级攻防:大规模DDOS防御架构
  10. Adobe中国授权培训中心操作说明—Adobe认证