一起不太引起注意的越界操作
谁都知道越界会带来很多问题。但究竟什么问题?会有什么后果,估计很少人能够说得清楚吧
C++缺少一些保护机制,因此,越界了之后,事实上是无法预估的,有很大可能会“没事”,有时候会莫名其妙错误
一般来说,对于一个成熟的C++开发者,或多或少会碰到越界的问题
最近在处理一个程序,经过跟踪发现一处隐藏的bug,一开始百思不得其解,后来算是搞清楚了。
为了保密的原因,隐去项目的信息,以下的代码均是测试代码,并非真实项目中的代码,但表达原理是一致的。
(so,测试代码就是测试代码,请不要直接copy使用,也不要拿代码规范去要求)
先来看看,代码:
//参数定义:注意123的顺序
static USHORT g_us1 = 1201;
static USHORT g_us2= 2202;
static USHORT g_us3 = 3203;
//实际调用代码:
void TestInput(int *nGet)
{
*nGet = 0;
}
printf("ori: %d %d %d \r\n",g_us1,g_us2,g_us3);
TestInput((int *)&g_us2); //这里我用了强制转换,是因为我当前的IDE是编译有错误,实际很多IDE是不用强制转换,也不会报错的。
printf("TestInput: %d %d %d \r\n",g_us1,g_us2,g_us3);
会打印什么数据?
问题不在g_us2本身,而是g_us3的值,莫名的被改变了,而且是悄无声息的被改掉。
这种结果 就不是我们自己想要的。
如果g_us3是一个循环判断,很有可能导致死循环;
这个问题就严重了,程序也很容易就挂掉的。
为了搞清楚这类事情,再改进下代码:
//参数定义:注意123的顺序(相对上面的,调整为312)
static USHORT g_us3 = 3203;
static USHORT g_us1 = 1201;
static USHORT g_us2= 2202;
//实际调用代码:
void TestInput(int *nGet)
{
*nGet = 0;
}
printf("ori: %d %d %d \r\n",g_us1,g_us2,g_us3);
TestInput((int *)&g_us2); //这里我用了强制转换,是因为我当前的IDE是编译有错误,实际很多IDE是不用强制转换,也不会报错的。
printf("TestInput: %d %d %d \r\n",g_us1,g_us2,g_us3);
此时输出:
这样貌似没有问题了,实际隐藏更深的问题,因为你都不知道哪个值被改掉了!!
为了弄得明白,再改进下:
//申明变量依然跟第一种一样:
static USHORT g_us1 = 1201;
static USHORT g_us2= 2202;
static USHORT g_us3 = 3203;
void TestInput(int *nGet)
{
*nGet = 0x12345678; //这里不再0,而是一个比较有特色的值
}
printf("ori: %d %d %d \r\n",g_us1,g_us2,g_us3);
TestInput((int *)&g_us2);
printf("TestInput: %d %d %d \r\n",g_us1,g_us2,g_us3);
而他本身存在低2字节
这个要跟具体编译,大小端问题(这点要说明下,单片机可能跟PC不一样)
再看一个例子:
static USHORT g_us1 = 1201;
static USHORT g_us2= 2202;
static UINT g_us3 = 0x09999999; //这里变成UINT类型,且给个比较大值
void TestInput(int *nGet)
{
*nGet = 0x12345678;
}
printf("ori: %d %d %d \r\n",g_us1,g_us2,g_us3);
TestInput((int *)&g_us2);
printf("TestInput: %d %d %d \r\n",g_us1,g_us2,g_us3);
大家可以思考下,会打印什么数据?
对的,一半的值被改掉了!这种人马组合,最恶心了,不仔细点还真发现不了
好了,对于一般情况下,当然这样使用也不多,但一个软件多多少少会涉及到一点,特别是一些单片机程序,有时候为了节省点2字节空间,减少点类型转换,就可能不小心就写成这样了。(挖了一个坑,而且不一定会有问题,比如说TestInput函数里并不是每次都赋值一个固定的值,比如说赋值0是在安全范围内,而1233333可能就是异常的源头)
这样的情况,危险也是隐藏的,或大或没有问题。比如说,上面的g_us3,本身在每次应用的时候,都是重新赋值的(用的时候,也做了一些有效性的判断),当然问题也没有那么大了,但不可以乐观!
我们实际项目中还存在一个前提:“TestInput”是个多处调用函数(通用函数),由于传入参数有些地方是4字节,有些是2字节,从而一股脑改成4字节,所以才会带来问题。
经过以上分析与举例,相信大家应该明白,这个隐藏问题产生的原理以及影响的范围。
自己也思考过,我们在设计的时候,如何避免:
- 这种作为“out”的参数(引用),必须类型一致,不要强制转换!此原则,同样适用于结构体!
- 如果实在不能做到类型一致,可以写两个不一样“TestInput”的函数。也可以用个临时变量先转换,转换成一致再调用。
- 如果可以的话,多用4字节类型,这两个字节省也省不了哪里去,反而带来隐患。毕竟4字节转2字节还是可控的,2字节转4字节就会有点隐患!
好了,以上就是我的一点思考与建议,我觉得比较有意思,希望对大家有所帮助启发。大家也可以回过头去看看,自己的代码是否存在类似情况!