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程序的源码啊。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。