当前位置:首页 > 公众号精选 > 漫长当下

这几天在完善前面的文章菜单式GUI的程序时遇到了一个BUG,功能代码是需要实现用按键来修改数值,并且将修改后的数值回写到给定变量中,最开始的想法是定义一个64位有符号的值用于临时的修改变量cur_v,再定义一个64位有符号的指针src_v指向需要修改的值的地址,我将这部分代码单独编写成了一个测试程序,其代码如下:

#include 
#include 
#include 

int64_t *src_v;
int64_t cur_v;
int32_t test_v;
int64_t max = 5;
int64_t min = -55;

int main(int argc,char *argv[])
{
    cur_v = -5;
    src_v = &test_v;

    printf("%p\r\n",src_v);
    *src_v = cur_v;
    printf("%p\r\n",src_v);
    printf("%ld\r\n",*src_v);

    if(*src_v < min)
    {
        printf("<\r\n");
    }
    else if(*src_v > max)
    {
        printf(">\r\n");
    }
    else
    {
        printf("+\r\n");
    }

    return 0;
}

用gcc编译其运行结果为:

0x55f52c116028

0x55f52c116028

-5

+

从这个结果来看,代码运行和结果都是没问题的,但为什么原程序中只要我将值修改成负数,按下”确认“键后再按下”加“键程序就崩溃了呢,所以我很确认这段代码一定有问题,只是没完全模拟出实际的情况,导致BUG没有复现出来,然后在原代码中经过排查,发现是按下“确认”键后回写修改值这一句的问题,但问题的原因却令人匪夷所思,猜测是栈溢出,但是程序却还有大量的栈空间未使用,我将test_v的类型改成int64_t,再测试发现程序运行正常了,但还未找到问题的原因,于是我将测试程序改成了下面的代码:

#include 
#include 
#include 

int64_t *src_v;
int64_t cur_v;
int32_t test_v[2];
int64_t max = 5;
int64_t min = -55;

int main(int argc,char *argv[])
{
    cur_v = -5;
    //情况1
    src_v = (int64_t *)&test_v[0];
    //情况2
//    src_v = (int64_t *)&test_v[1];

    printf("%p\r\n",src_v);
    *src_v = cur_v;
    printf("%p\r\n",src_v);
    printf("%ld\r\n",*src_v);
    printf("%x,%x\r\n",test_v[0],test_v[1]);

    if(*src_v < min)
    {
        printf("<\r\n");
    }
    else if(*src_v > max)
    {
        printf(">\r\n");
    }
    else
    {
        printf("+\r\n");
    }

    return 0;
}

此次运行结果:

情况1:

0x55f94339e028

0x55f94339e028

-5

fffffffb,ffffffff

+

情况2:

0x55e83113f02c

0x55e8ffffffff

段错误 (核心已转储)

这里可以看到情况1程序虽然没崩溃,但是test_v数组第二个元素也有值,而且值为0xfffffff,按理说其值应该为0才对,来看第二种情况src_v的地址发生了变化,紧接着再次访问时出现了段错误,既然这样那原程序肯定也是同样的问题导致程序崩溃了,找到问题了,那就来分析原因吧,可以看到src_v地址只有低32位被修改了,而高32位是正确的,被修改的低32位的值刚好也是0xffffffff,看到这儿原因肯定是栈溢出内存访问越界导致内存踩踏没错了,那为什么正数是正确的,而负数就不正确了呢,这也正是解决问题的关键所在,我们都知道负数的存储方式是补码,那int64_t类型的-5在内存中的存储值就是0xfffffffffffffffb,这样想,那问题就迎刃而解了,执行*src_v = cur_v;时,C语言并未执行自动截断的操作,因为指针类型也是int64_t的,所以这个操作被骗了,但是与src_v绑定的变量却是int32_t类型的,执行回写操作后低32位被保存到了其绑定的变量中,而高32位则覆盖了紧邻的内存,出现了内存踩踏事件,而导致程序崩溃,而且程序崩溃的地方还是随机的,就看被踩的内存是谁了,知道了原因后,那修改就简单了,只需要注意类型一定要与指针指向的变量类型一致;还有种方式就是不使用指针,直接将修改后的cur_v的值赋给test_v即可,这样的话两边类型就算不一致也会触发C语言的自动截断,而不会出现内存踩踏事件。

最后附上我最终修改后的代码:

#include 
#include 
#include 

//这样使用此指针时必须进行类型转换就不会忘记而导致出错
void *src_v;
int64_t cur_v;
int32_t test_v;
int64_t max = 5;
int64_t min = -55;

int main(int argc,char *argv[])
{
    int64_t tmp;

    cur_v = -5;
    src_v = &test_v;

    printf("%p\r\n",src_v);
    //这里注意类型与test_v保持一致
    *(int32_t *)src_v = (int32_t)cur_v;
    printf("%p\r\n",src_v);
    //这里注意类型与test_v保持一致
    tmp = *(int32_t *)src_v;
    printf("%ld\r\n",tmp);

    if(tmp < min)
    {
        printf("<\r\n");
    }
    else if(tmp > max)
    {
        printf(">\r\n");
    }
    else
    {
        printf("+\r\n");
    }

    return 0;
}

运行结果:

0x55a85dbd1028

0x55a85dbd1028

-5

+

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭