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程序的源碼啊。