Chinaunix

标题: 百思不得其解的字符串问题 - 谜底揭开,编译器根据memcpy操作位数臆断内存分配 [打印本页]

作者: ecloud    时间: 2015-06-03 13:26
标题: 百思不得其解的字符串问题 - 谜底揭开,编译器根据memcpy操作位数臆断内存分配
本帖最后由 ecloud 于 2015-06-05 01:50 编辑
  1. /* Get RA and DEC from the mount*/
  2.           write(fd,":GEC#",5);
  3.         usleep(500000);
  4.         n = read(fd,RADEC,20);
  5.         RADEC[n] = 0;
  6.         /* Get DEC*/
  7.         dec_sign = RADEC[0];
  8.         memcpy(DEC,&RADEC[1],8);
  9.         DEC[8] = 0;
  10.         dec = atoi(DEC);
  11.         dec_d = dec/360000;
  12.         dec_m = (dec-dec_d*360000)/6000;
  13.         dec_s = (dec-dec_d*360000-dec_m*6000)/100;
  14.         /* Get RA*/
  15.         memcpy(RA,&RADEC[9],8);
  16.         RA[8] = 0;
  17.         ra = atoi(RA);
  18.         ra_h = ra/3600000;
  19.         ra_m = (ra-ra_h*3600000)/60000;
  20.         ra_s = (ra-ra_h*3600000-ra_m*60000)/1000;
复制代码
这段代码的逻辑是,从串口读取一个17位长字符串RADEC,第0位符号,1-8给DEC转整数,9-16给RA转整数。目前为止功能实现正常,但是……

以下是第一次gdb的结果

  1. 50                n = read(fd,RADEC,20);
  2. 1: RADEC = "\000\000\000\000H\214\001@\001\000\000\000\000\000\000\000\030\217\001@"
  3. (gdb)
  4. 51                RADEC[n] = 0;
  5. 1: RADEC = "+3240000000642750#\001@"
  6. (gdb)
  7. 53                dec_sign = RADEC[0];
  8. 1: RADEC = "+3240000000642750#\000@"
  9. (gdb)
  10. 54                if (RADEC[0]=='+') dec_sign = 1;
  11. 1: RADEC = "+3240000000642750#\000@"
  12. (gdb)
  13. 55                if (RADEC[0]=='-') dec_sign = -1;
  14. 1: RADEC = "+3240000000642750#\000@"
  15. (gdb)
  16. 56                memcpy(DEC,&RADEC[1],8);
  17. 1: RADEC = "+3240000000642750#\000@"
  18. (gdb)
  19. 57                DEC[8] = 0;
  20. 1: RADEC = "+3240000000642750#\000@"
  21. (gdb)
  22. 58                dec = atoi(DEC);
  23. 1: RADEC = "\000\063\062\064\060\060\060\060\060\060\060\066\064\062\067\065\060#\000@"
复制代码
也就是说从57行 DEC[8] = 0;开始,RADEC的第一个字节被清0

后来经过研究发现,是编译器根据memcpy的操作长度臆断用户需要的内存空间,少给DEC分配了一个字节的内存,并且跟RADEC紧密相连,造成了DEC[8]跟RADEC[0]是同一个地址!

惊出了我一身冷汗,幸亏我这里只错了一位,没有造成太大影响,并且很凑巧的发现了这个问题。不然的话后果不堪设想,内存泄露就是这么很“正常”的产生了
我没试过X86平台是不是也这个德行,但是就ARM平台的这种情况来看,我要对gcc说一百句草泥马,你TM用得着这么帮我省内存吗???
作者: hellioncu    时间: 2015-06-03 14:01
DEC[8] = 0;后面没有打印呀,这“无法显示”指什么?
作者: ecloud    时间: 2015-06-03 19:50
本帖最后由 ecloud 于 2015-06-03 23:24 编辑

printf("%s\n",RADEC);显示为空,在第10行以后的话
吊诡的是后面对[9]到[16]的读取竟然没影响,所以怀疑跟编译器行为有关
作者: ecloud    时间: 2015-06-03 20:02
会不会是 -O2引起的?编译器判断RADEC不再有用就给释放了?那太深了,自动内存回收啊……
作者: folklore    时间: 2015-06-03 22:23
回复 4# ecloud


    你都Printf了, 谁敢说没用了?
不会你在GDB中Print的吧? 加上O2以后?
要是那样, 请自已到北韩申请狗决。












2

作者: folklore    时间: 2015-06-03 22:34
回复 5# folklore


    如果是这样的话, 加上有问题的变量是本地变量的话, 只能说内存被复用了。 你
自已写个MEMCPY代替memcpy试试, 对MEMCPY(最好在其它编译单元)编译器可能就不敢乱复用了。
作者: ecloud    时间: 2015-06-03 23:18
回复 6# folklore
这也太疯狂了吧……
它凭什么要复用啊我这程序也不大
难道是memcpy在ARM平台上有bug?
等我回去用GDB跟跟看吧,实在不行在X86上编译一个一样的跑跑看

如果说memcpy在ARM上真有此等bug,那我整套所有的程序都将处于危险的境地了,这也太可怕了……

   
作者: folklore    时间: 2015-06-04 18:31
回复 7# ecloud


    这有什么疯狂的。 memcpy是标准函数, 编译器知道它要干什么, 你写个MEMCPY,它不知道这个函数干吗的, 不敢优化啊。
作者: ecloud    时间: 2015-06-05 01:41
  1. 55                if (RADEC[0]=='-') dec_sign = -1;
  2. 1: RADEC = "+3240000000642750#\000@"
  3. (gdb)
  4. 56                memcpy(DEC,&RADEC[1],8);
  5. 1: RADEC = "+3240000000642750#\000@"
  6. (gdb)
  7. 57                DEC[8] = 0;
  8. 1: RADEC = "+3240000000642750#\000@"
  9. (gdb)
  10. 58                dec = atoi(DEC);
  11. 1: RADEC = "\000\063\062\064\060\060\060\060\060\060\060\066\064\062\067\065\060#\000@"
复制代码
原来是 DEC[8] = 0;这条语句的问题,DEC字符串在初始化的时候可能地址分配的不对,正好它的[8]是RADEC的[0],就造成了这种结果
可能是ARM上面编译器对内存使用太过吝啬,造成两个字符串紧密相连,因为memcpy只做了8位,致使编译器认为[8]无用,于是一个定义成9个字节的内存空间实际上就给分配了8个字节
我个人认为这是一个相当严重的bug,尤其是当我没有使用O2的时候也这样紧密排列内存空间,基于memcpy来臆断用户定义的变量有多长
解决方法两种
一是定义的时候多一位,定义成DEC[9],就跟后面留出一个缓冲位
二是不用\0去封闭字符串,也就是删掉DEC[8] = 0这句话。这种操作的前提是你的变量数值本身精确对齐,并且不会再对其使用其他的字符串函数(需要\0来判断字符串结束的)
我最后还是采用了第一种方法,虽然我的程序本身数据非常规整毫无例外的可能性,但是也还是给自己留了一个缓冲,封闭了字符串,安全至上!
作者: hellioncu    时间: 2015-06-05 08:45
ecloud 发表于 2015-06-05 01:41
原来是 DEC[8] = 0;这条语句的问题,DEC字符串在初始化的时候可能地址分配的不对,正好它的[8]是RADEC的[0] ...


"一是定义的时候多一位,定义成DEC[9]",难道你本来定义的是DEC[8]?那出这个问题很正常啊
作者: cokeboL    时间: 2015-06-05 09:16
看到[8] = 的时候就觉得别扭,还真是。。

作者: lxyscls    时间: 2015-06-05 10:19
编译器是否干了不好的事,上汇编,撸主
作者: serenemoon    时间: 2015-06-05 13:01
hellioncu 发表于 2015-06-05 08:45
"一是定义的时候多一位,定义成DEC[9]",难道你本来定义的是DEC[8]?那出这个问题很正常啊


我本来想问一下楼主是否定义的时候是 DEC[8]的,但看到楼主arm,gcc,gdb都上来了,觉得应该不会出这么简单的错误,就没问...而且楼主也没贴DEC的定义。
作者: folklore    时间: 2015-06-05 20:38
回复 10# hellioncu


    楼主太牛B了, 太牛B了, MS楼主已经写了好多年程序了, 太牛B了。
作者: think1008    时间: 2015-06-07 22:39
lol牛B




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2