Gcc编译时无优化参数,以前曾经被-O坑过。
#include<stdio.h> #include<string.h> intmain() { charurl[512]; sprintf(url,"218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4"); printf("%s\n",url); char*p=url; strcpy(p+15,p+22); printf("%s\n",url); return0; }
打印结果应该如下
218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4
218.26.242.56/06d6168bf1a7294ae0e1c071171adcd48.mp4
但是在centos6.3系统下,gcc4.4.7,
打印结果会是
218.26.242.56/0/0/1023/6d6168bf1a7294ae0e1c071171adcd48.mp4
218.26.242.56/0/f1a7294a1a7294ae0e1c071171adcd48.mp4
目前实验redhat5.05.7,centos7.2系统下都不会出现问题,唯有6.x(试了6.0、6.3、6.7)的gcc4.4.7会有问题
下载4.4.7源码
wget http://ftp.gnu.org/gnu/gcc/gcc-4.4.7/gcc-4.4.7.tar.bz2
代码如下
externvoidabort(void); externintinside_main; char* strcpy(char*d,constchar*s) { char*r=d; #ifdefined__OPTIMIZE__&&!defined__OPTIMIZE_SIZE__ if(inside_main) abort(); #endif while((*d++=*s++)); returnr; }
理论上不应该出现如此问题
Centos6.x的strcpy源码为汇编码
char*strcpy(char*dest,constchar*src) { return__kernel_strcpy(dest,src); } staticinlinechar*__kernel_strcpy(char*dest,constchar*src) { char*xdest=dest; asmvolatile("\n" "1:move.b(%1)+,(%0)+\n" "jne1b" :"+a"(dest),"+a"(src) ::"memory"); returnxdest; }
同样看不出有什么问题。
网络上也找到过另外一种优化版本的strcpy代码,使用寄存器加速效果,在网上找到的号称gcc的优化代码也是类似
char* strcpy(dest,src) char*dest; constchar*src; { registercharc; char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src); constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1; size_tn; do { c=*s++; s[off]=c; } while(c!='\0'); n=s-src; (void)CHECK_BOUNDS_HIGH(src+n); (void)CHECK_BOUNDS_HIGH(dest+n); returndest; }
将之作为自定义函数使用后发现也没有问题。
继续发现strncpy和sprintf也会遇到同样的问题。
采用memcpy就没有问题了
memcpy(p+11,p+18,strlen(p+18)+1);
看了下源码,跟strcpy也没什么区别
void* memcpy(void*dest,constvoid*src,size_tlen) { char*d=dest; constchar*s=src; while(len--) *d++=*s++; returndest; }
暂时不明白为什么strcpy、strncpy、sprintf在gcc4.4.7下,自我移动会导致问题。
以前曾经在网上看见过strcpy的优化函数,在64位系统里,采用八字节长整形来进行复制,但是未在库中见过,只是作为优化的自定义代码推荐。
在这里例子中,如果我们将p+15改成p+16,就一切正常,把字符串总长度缩小到p+32(即*(p+32)=0),那么也一切正常。错误字段长度8字节,跟8都有关系,怀疑系统在什么地方做了优化,但是实在搞不清是谁在优化,优化后的代码是什么样子的。
所以建议如果要进行字符串自我移动,不要使用str,使用mem函数。
--------------------
同事提供了一个帖子,说的是内存重叠的问题
http://www.jb51.cc/article/p-smdmucpb-xt.html
但是这个例子的作者其实没有分析到点子上
charstr[]="123456789"; strcpy(str+2,str);
本身在代码逻辑上就是错的,从源码就能看出来,从前往后复制,会导致后面的内存覆盖。和我们这次遇到的不是一个情况。
按照源码应该结果是1212121212121212。。。。。。。。。。。一直到越界崩溃
但是实际结果是121234565678
在几个机器上试了下
在gcc4.1.1上,是12121212121。。。。。。 崩溃
Gcc4.4.7 显示121234565678
gcc4.8.5 显示12123456789
应该是在4.4.7上确实有优化,但是4.8.5应该是解决了,而且连这种逻辑异常的也一起支持了。
在网上找到了gcc4.7上strcpy的汇编bug,为什么是不是也有关系。
-----------------
网上查了一下,发现被误导了,这里应该研究libc.so的代码,而不是看gcc的代码
看了下libc的源码,define了不少实现,在不同设备上使用了不同优化的汇编码,应该是使用8字节复制代替单个字符串复制,在libc2.12有bug,libc2.17上应该是解决了。
查看他们的strcpy代码
libc2.12.1上
/*CopySRCtoDEST.*/ char* strcpy(dest,src) char*dest; constchar*src; { reg_charc; char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src); constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1; size_tn; do { c=*s++; s[off]=c; } while(c!='\0'); n=s-src; (void)CHECK_BOUNDS_HIGH(src+n); (void)CHECK_BOUNDS_HIGH(dest+n); returndest; }
libc2.17上
/*CopySRCtoDEST.*/ char* strcpy(dest,src) char*dest; constchar*src; { charc; char*__unboundeds=(char*__unbounded)CHECK_BOUNDS_LOW(src); constptrdiff_toff=CHECK_BOUNDS_LOW(dest)-s-1; size_tn; do { c=*s++; s[off]=c; } while(c!='\0'); n=s-src; (void)CHECK_BOUNDS_HIGH(src+n); (void)CHECK_BOUNDS_HIGH(dest+n); returndest; }
发现2.17相比2.12没什么改动,就是取消了寄存器(reg_char变成了char),在这个博客里面说过寄存器的重要性,可以提高copy速度,不明白为什么2.17取消了寄存器。
http://blog.csdn.net/astrotycoon/article/details/8114786
继续测试
发现在libc2.12上
(gdb)bt #00x00000036a7532664in__strcpy_ssse3()from/lib64/libc.so.6 #10x0000000000400671inmain()attest.c:32
真正使用的是ssse3指令集下的strcpy.S实现
在glib2.17下
(gdb)bt #0__strcpy_sse2_unaligned()at../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:232 #10x0000000000400655inmain()attest.c:9
直接进入.s汇编码,连libc.so都没有进入,不过这个汇编函数strcpy-sse2-unaligned也是glib里面的
对汇编实在无力,完全无从下手。
不过看来所谓的c源码对分析没有什么太大的帮助还容易引起误解,因为底层的库根本就不用c程序的源码啊。
原文链接:https://www.f2er.com/centos/377974.html