全局变量的使用与数据断点调试
(2007-05-19 15:42:03)
先前参与的项目,虽然使用的是面向过程的C语言,但软件框架设计,模块的封装,数据处理独立于菜单实现等等,使得软件具有面向对象的优点。面向对象可以使我们尽量的避免使用全局变量。因为学校的老师,公司的前辈都在强调全局变量在面向过程中的隐患。而如今我接手的项目,里面却使用了很多全局变量,软件完全是面向过程的设计方法。突如其来的这么多全局变量,让人感觉不适应,而恰恰让我遇上了一次全局变量带来的问题。记录和总结一下问题的解决过程。
在处理一个问题的时候,发现一个全局变量(char TempStatus;)的值偶尔出现不是我所期望的,于是向前追溯,找到最后一个赋值语句 TempStatus = 1;在这一过程中没有再修改过,在赋值后加打印,竟然出现前后连续两个printf输出的该变量的值不一样的情况,更别说后面再用了。出现这样的情况,有两种可能,一是该全局变量在别的进程中被修改了;二是与该全局变量地址接近的某个全局变量出现越界了。要是第一种情况还比较好处理,如果是第二中情况,那就非常棘手了。
先排除第一种情况,要确认是否是在别的进程被修改了,就需要确认在什么地方修改的。搜索该全局变量,好家伙,出来一大屏幕,汗!!看一边都得半天,要在每个地方加一个printf逐一排除,那得多长时间啊,郁闷!思路受阻。此时不禁怀恋set与get的好,你说要是写上以下这样的两个函数给别的地方调用该多好啊!
void SetTempStatus(char val)
{
TempStatus = val;
}
void GetTempStatus(voild)
{
return TempStatus;
}
同时将TempStatus 定义为static char TempStatus;在要用的时候调用get函数,在要赋值的地方调用set函数。这样一来,我要确定是否有别的地方修改就只要在set函数里面加个printf就可以了,确定是被别的地方修改后只要在set函数里面加个断点,一单步就立刻定位修改的地方了。多简单!可实际项目中并没有这样设计,没办法了。
那就排除溢出情况吧。一般溢出都是该全局变量前的变量越界操作引起。于是找到该变量定义的地方:
int iDataSize;
char TempStatus;
unsigned int DataLen;
struct_table DataTable[40]={0};
stuct_table1 DataTable1[20];
前面是一个整形数据,出现越界操作的不太可能,莫非是后面的数组?一搜索,又是一大屏,而且难度更大,你不知道它什么时候在什么地方出现越界溢出,汗,汗!!!
就这样整整郁闷了大半天,没法确定问题的根源,也就没法彻底解决问题,毫无进展。脑门子冒汗了,休息休息再说,还别说有的时候无路可走了,放松一下回来真有豁然开朗的感觉。全局变量的值不是我期望的,而我自己没有做赋值,那肯定就是被修改了,不管是什么原因,最终就是要确定它在什么地方被修改的。于是想到了,项目方案的一个可视化工具里面有一个数据断点(DataBreakpoint)的功能,也许使用这一功能也许就能找到意外修改的地方。
可视化工具在工作中很少有同事用,数据断点就更少有人用。这也是我第一次用,凭借一点理解试一下。理解中数据断点,应该是对某一变量的地址所对应的空间进行监控,一旦该地址对应的空间被修改,就会立即产生中断,并将程序的运行指针指向修改的语句。由此肯定能确定问题所在。
因为全局变量在很多地方都有赋值,如果一开始就给该变量设数据断点(DataBreakpoint),肯定会产生很多中断。而我只需要关注该变量在我赋值以后的变化情况,由此在我的赋值语句TempStatus = 1;处先设一个代码断点(Code Breakpoint)让程序先运行到这里,然后设置该变量的数据断点,让程序继续运行,这个时候一个很奇怪的中断出现了,程序的运行指针指在语句 DataTable1[iIndex].value = 0;程序在一个毫不相干的赋值语句中断了,那肯定是数组DataTable1越界修改了TempData对应的空间,喜!再看该数组的下标,竟然是-1。问题找到了,再看看该语句所在的函数:
int GetDataIndex(char data)
{
.....
for(i=0;i<DataLen;i++)
{
if(DataTable[i].value == data)
return i/2;
}
return -1;
}
void CreateTable(void)
{
int iIndex;
........
iIndex = GetDataIndex(data);
.........
DataTable1[iIndex].value = 0;
}
一看问题出来了,函数CreateTable()没有对变量iIndex作出错处理,在iIndex出错等于-1时导致后续的数据越界操作。向前越界结构体stuct_table1的大小,刚好使得value指向了TempStatus。问题解决就很容易了,只要将函数改成:
void CreateTable(char data)
{
int iIndex;
........
iIndex = GetDataIndex(data);
if(-1 == iIndex)
return;
.........
DataTable1[iIndex].value = 0;
}
往往是这样,找问题的根源要用九牛二虎之力,而解决问题只需吹灰之力;
解决了问题,回过头来,看看变量的定义。TempStatus 和DataTable1之间还隔了一个很大的数组,他两怎么会“勾搭”上的?做个实验,打印这些变量的地址,发现数组DataTable的地址与其他几个有很大区别,有同事说,是因为它被初始化赋初值了,跟其他几个全局变量不在一个区域。这样也就好理解为什么DataTable1能够跨过一个大数组跟TempData扯上关系。
问题解决了,总结一下。一:还是要强调尽量避免使用全局变量,如果非用不可,毕竟还是面向过程设计,应该想到使用set和get,并用static定义加以限制。这样可以减少不少的麻烦,也便于以后查找问题;二,就是要确保函数的健壮性,要有意识的去考虑出错的情况,并做出正确的处理。这样的处理是非常有必要的,往往一些隐患就在于没有对出错情况做出正确的处理;三,就是数据断点的使用,这是个好东西,第一次使用就深深体会了它的好。
(注:这里用到的代码非实际代码,只是为说明问题,出之随意)