C语言给指针指向的变量赋值一个负数而产生的BUG
这几天在完善前面的文章菜单式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
+





