strcpy()與strncpy()的區別


頭文件:#include <string.h>
strcpy() 函數用來複制字符串,其原型爲:
char *strcpy(char *dest, const char *src);
【參數】dest 爲目標字符串指針,src 爲源字符串指針。
注意:src 和 dest 所指的內存區域不能重疊,且dest 必須有足夠的空間放置 src 所包含的字符串(包含結束符NULL)
【返回值】成功執行後返回目標數組指針 dest。
strcpy() 把src所指的由NULL結束的字符串複製到dest 所指的數組中,返回指向 dest 字符串的起始地址。
注意:如果參數 dest 所指的內存空間不夠大,可能會造成緩衝溢出(buffer Overflow)的錯誤情況,在編寫程序時請特別留意,或者用strncpy()來取代。



strncpy()用來複制字符串的前n個字符,其原型爲:
    char * strncpy(char *dest, const char *src, size_t n);
【參數說明】dest 爲目標字符串指針,src 爲源字符串指針。
strncpy()會將字符串src前n個字符拷貝到字符串dest。
不像strcpy(),strncpy()不會向dest追加結束標記'\0',這就引發了很多不合常理的問題,將在下面的示例中說明。

如果src的前n個字節不含NULL字符,則結果不會以NULL字符結束。        
如果src的長度小於n個字節,則以NULL填充dest直到複製完n個字節。        
src和dest所指內存區域不可以重疊且dest必須有足夠的空間來容納src的字符串。    
注意:src 和 dest 所指的內存區域不能重疊,且dest 必須有足夠的空間放置n個字符
【返回值】返回指向dest的指針(該指向dest的最後一個元素)    



1. strcpy

我們知道,strcpy 是依據 \0 作爲結束判斷的如果 to 的空間不夠,則會引起 buffer overflow
strcpy 常規的實現代碼如下(來自 OpenBSD 3.9):

char *
strcpy(char *to, const char *from)
{
       char *save = to;

       for (; (*to = *from) != '\0'; ++from, ++to);
       return(save);
}

但通常,我們的 from 都來源於用戶的輸入,很可能是非常大的一個字符串,因此 strcpy 不夠安全。


2. strncpy

在 ANSI C 中,strcpy 的安全版本是 strncpy。

char *strncpy(char *s1, const char *s2, size_t n);

但 strncpy 其行爲是很詭異的(不符合我們的通常習慣)。標準規定 n 並不是 sizeof(s1),而是要複製的 char 的個數。一個最常見的問題,就是 strncpy 並不幫你保證 \0

結束。

char buf[8];
strncpy( buf, "abcdefgh", 8 );

看這個程序,buf 將會被 "abcdefgh" 填滿,但卻沒有 \0 結束符了。

另外,如果 s2 的內容比較少,而 n 又比較大的話,strncpy 將會把之間的空間都用 \0 填充。這又出現了一個效率上的問題,如下:

char buf[80];
strncpy( buf, "abcdefgh", 79 );

上面的 strncpy 會填寫 79 個 char,而不僅僅是 "abcdefgh" 本身,還包含71 (79-8)個  '\0' 。


strncpy 的標準用法爲:(手工寫上 \0)

strncpy(path, src, sizeof(path) - 1);
path[sizeof(path) - 1] = '\0';
len = strlen(path);

--------------------------------------------------------------------------

注意:定義一個 字符串數組的時候,一定要留一個字節給空字符'\0'。

char qq[8] = "qwer123" 

char ww[8] = "qwer1234"

printf("%s\n",qq);

printf("%s\n",ww);

執行這條語句後,打印的結果都是一樣的。都是  qwer123 ,因爲 ww[8]的最後一個字節被'\0'填充了。



還有一些講了strncpy注意事項的博客,寫的不錯。

http://blog.csdn.net/stpeace/article/details/22581763  (別再耍流氓了: 請別再用strcpy, 而用strncpy)

http://blog.haipo.me/?p=1065             (停止使用 strncpy 函數!)(這篇文章重點看)




停止使用 strncpy 函數!

也許你曾經被多次告知,要使用 strncpy 替代 strcpy 函數,因爲 strncpy 函數更安全。而今天我要告訴你,strncpy 是不安全的,並且是低效的,strncpy 的存在是由於一個歷史原因造成的,你不應當再使用 strncpy 函數。

下面我來解釋爲什麼 strncpy 函數是不安全並且是低效的,以及我們應該使用那些替代函數。

我以前對 strncpy 一直存在誤解,直到有一次出現了 BUG。好不容易定位到 strncpy 身上,然後仔細查看文檔,才明白問題所在。

誤解一:如果src 長度小於 n, 那麼strncpy 和 strcpy 效果一樣?

錯,事實上,strncpy 還會把 dest 剩下的部分全部置爲 0

一直認爲 strncpy 只是比 strcpy 多了長度校驗,卻不知道 strncpy 會把剩下的部分全置爲 0(粗體部分)。

char *strncpy(char *dest, const char *src, size_t n);
DESCRIPTION
The strcpy() function copies the string pointed to by src (including the terminating `\0′ character) to the array pointed to by dest. The strings may
not overlap, and the destination string dest must be large enough to receive the copy.
The strncpy() function is similar, except that not more than n bytes of src are copied. Thus, if there is no null byte among the first n bytes of src,
the result will not be null-terminated.

In the case where the length of src is less than that of n, the remainder of dest will be padded with null bytes.

這會導致什麼後果呢?

首先,如果 strncpy 的長度填錯了,比如比實際的長,那麼就可能會把其他數據清 0 了。我就遇到過這個問題,在後來檢查代碼看到這個問題時,也並不以爲然,因爲拷貝的字符串不可能超過緩衝區的長度。

另外,假設 dest 的長度爲 1024, 而待拷貝的字符串長度只有 24,strncpy 會把餘下的 1000 各字節全部置爲 0. 這就可能會導致性能問題,這也是我爲什麼說 strncpy 是低效的。

誤解二:如果src 長度大於等於 n, 那麼 strncpy 會拷貝 n – 1 各字符到 dest, 然後補 0?

錯,大錯特錯,罰抄上面的 DESCRIPTION ,直到看到:

if there is no null byte among the first n bytes of src, the result will not be null-terminated.

這就可能導致了不安全的因素。

如果待拷貝字符串長度大於了 n, 那麼 dest 是不會有結尾字符 0 的。假設這樣一種情況:

char s[] = "hello world";
strncpy(s, "shit!", 5);
puts(s);

輸出的結果是 “shit” 還是 “shit! world” ?

這種情況只是導致了輸出結果錯誤,嚴重的,如果 dest n 字節後面一直沒有 0,那麼就會導致程序段錯誤。

strncpy 最開始引入標準庫是用來處理結構體中固定長度的字符串,比如路徑名,而這些字符串的用法不同於 C 中帶結尾字 0 的字符串。所以 strncpy 的初衷並不是一個安全的 strcpy.

那麼用那些函數來替代 strncpy?

1、使用 snprintf

snprintf(dest, n, src);

的效果和我們對一個安全的字符串拷貝函數的期望完全一致。

但是這個函數效率有點問題,並且特殊字符比如 %d 會轉義。

2、自己實現一個高效並且安全的字符串拷貝函數 sstrncpy,開頭的 s 代表 safe

/* safe strncpy */
 
char *sstrncpy(char *dest, const char *src, size_t n)
{
    if (n == 0)
        return dest;
 
    dest[0] = 0;
 
    return strncat(dest, src, n - 1);
}

使用 strncat 是因爲很難實現一個性能能夠達到庫函數的字符串拷貝函數。

3、但是,上面兩個函數都有一個問題:如果不能預知 src 的最大長度,那麼 src 會被靜默的截斷。

如果是爲了複製一個字符串,那麼更好的做法是使用 strdup 函數

char* strdup (const char *s);

strdup 函數會調用 malloc 分配足夠長度的內存並返回。

當然,你需要在你不使用的時候 free 它。

如果只是函數內部調用,也可以使用 strdupa 函數。

char* strdupa (const char *s);

strdupa 函數調用 alloca函數而非 malloc 函數分配內存,alloca 分配的內存是桟內存而非堆內存。所以當函數返回後,內存就自動釋放了,不需要 free。

4、如果是從文本文件中讀數據,相對與 fgets 函數,更好的做法是使用 getline

ssize_t getline (char **lineptr,size_t *n,FILE *stream);

一個簡單的例子:

char *line = NULL;
size_t len = 0;
 
while (getline(&line, &len, stdin) != -1)
{
    fputs(line, stdout);
}
 
free(line);

當 line 爲 NULL 或者 len 爲 0 時,getline 調用 malloc 分配足夠大的內存。所以你需要在用完後 free 它們。

和 fgets 相同,getline 得到的行是帶換行字符的。

所以,忘了 strncpy 吧,血的教訓,說出來都是淚…


發佈了191 篇原創文章 · 獲贊 363 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章