一起不太引起注意的越界操作

谁都知道越界会带来很多问题。但究竟什么问题?会有什么后果,估计很少人能够说得清楚吧

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字节,所以才会带来问题。

 

       经过以上分析与举例,相信大家应该明白,这个隐藏问题产生的原理以及影响的范围。

       自己也思考过,我们在设计的时候,如何避免:

  1. 这种作为“out”的参数(引用),必须类型一致,不要强制转换!此原则,同样适用于结构体!
  2. 如果实在不能做到类型一致,可以写两个不一样“TestInput”的函数。也可以用个临时变量先转换,转换成一致再调用。
  3. 如果可以的话,多用4字节类型,这两个字节省也省不了哪里去,反而带来隐患。毕竟4字节转2字节还是可控的,2字节转4字节就会有点隐患!

 

好了,以上就是我的一点思考与建议,我觉得比较有意思,希望对大家有所帮助启发。大家也可以回过头去看看,自己的代码是否存在类似情况!

posted @ 2021-04-07 08:37  小刚学长  阅读(26)  评论(0编辑  收藏  举报