Gcc编译时无优化参数,以前曾经被-O坑过。
#include <stdio.h> #include <string.h> int main() { char url[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); return 0; }
打印结果应该如下
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
代码如下
extern void abort (void); extern int inside_main; char * strcpy (char *d, const char *s) { char *r = d; #if defined __OPTIMIZE__ &&!defined __OPTIMIZE_SIZE__ if (inside_main) abort (); #endif while ((*d++ = *s++)); return r; }
理论上不应该出现如此问题
Centos6.x的strcpy源码为汇编码
char *strcpy(char *dest, const char *src) { return __kernel_strcpy(dest, src); } static inline char *__kernel_strcpy(char*dest, const char *src) { char *xdest = dest; asm volatile ("\n" "1: move.b (%1)+,(%0)+\n" " jne 1b" : "+a" (dest), "+a" (src) : : "memory"); return xdest; }
同样看不出有什么问题。
将系统函数修改为自定义函数,使用一样的代码,结果均为正确。
网络上也找到过另外一种优化版本的strcpy代码,使用寄存器加速效果,在网上找到的号称gcc的优化代码也是类似
char * strcpy (dest, src) char *dest; const char *src; { register char c; char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src); const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1; size_t n; do { c = *s++; s[off] = c; } while (c != ‘\0‘); n = s - src; (void) CHECK_BOUNDS_HIGH (src + n); (void) CHECK_BOUNDS_HIGH (dest + n); return dest; }
将之作为自定义函数使用后发现也没有问题。
继续发现strncpy和sprintf也会遇到同样的问题。
采用memcpy就没有问题了
memcpy(p+11,p+18,strlen(p+18)+1);
看了下源码,跟strcpy也没什么区别
void * memcpy (void *dest, const void *src, size_t len) { char *d = dest; const char *s = src; while (len--) *d++ = *s++; return dest; }
暂时不明白为什么strcpy、strncpy、sprintf在gcc4.4.7下,自我移动会导致问题。
以前曾经在网上看见过strcpy的优化函数,在64位系统里,采用八字节长整形来进行复制,但是未在库中见过,只是作为优化的自定义代码推荐。
在这里例子中,如果我们将p+15改成p+16,就一切正常,把字符串总长度缩小到p+32(即*(p+32)=0),那么也一切正常。错误字段长度8字节,跟8都有关系,怀疑系统在什么地方做了优化,但是实在搞不清是谁在优化,优化后的代码是什么样子的。
所以建议如果要进行字符串自我移动,不要使用str,使用mem函数。
--------------------
同事提供了一个帖子,说的是内存重叠的问题
http://blog.csdn.net/stpeace/article/details/39456645
但是这个例子的作者其实没有分析到点子上
char str []="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上
/* Copy SRC to DEST. */ char * strcpy (dest, src) char *dest; const char *src; { reg_char c; char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src); const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1; size_t n; do { c = *s++; s[off] = c; } while (c != ‘\0‘); n = s - src; (void) CHECK_BOUNDS_HIGH (src + n); (void) CHECK_BOUNDS_HIGH (dest + n); return dest; }
libc2.17上
/* Copy SRC to DEST. */ char * strcpy (dest, src) char *dest; const char *src; { char c; char *__unbounded s = (char *__unbounded) CHECK_BOUNDS_LOW (src); const ptrdiff_t off = CHECK_BOUNDS_LOW (dest) - s - 1; size_t n; do { c = *s++; s[off] = c; } while (c != ‘\0‘); n = s - src; (void) CHECK_BOUNDS_HIGH (src + n); (void) CHECK_BOUNDS_HIGH (dest + n); return dest; }
发现2.17相比2.12没什么改动,就是取消了寄存器(reg_char变成了char),在这个博客里面说过寄存器的重要性,可以提高copy速度,不明白为什么2.17取消了寄存器。
http://blog.csdn.net/astrotycoon/article/details/8114786
继续测试
发现在libc2.12上
(gdb) bt #0 0x00000036a7532664 in __strcpy_ssse3 () from /lib64/libc.so.6 #1 0x0000000000400671 in main () at test.c:32
真正使用的是ssse3指令集下的strcpy.S实现
在glib2.17下
(gdb) bt #0 __strcpy_sse2_unaligned () at ../sysdeps/x86_64/multiarch/strcpy-sse2-unaligned.S:232 #1 0x0000000000400655 in main () at test.c:9
直接进入.s汇编码,连libc.so都没有进入,不过这个汇编函数strcpy-sse2-unaligned也是glib里面的
对汇编实在无力,完全无从下手。
不过看来所谓的c源码对分析没有什么太大的帮助还容易引起误解,因为底层的库根本就不用c程序的源码啊。