- C和C++安全編碼(原書第2版)
- (美)Robert C.Seacord
- 1833字
- 2020-10-30 17:56:44
2.5.6 C99
并非所有對strcpy()函數的使用都是有缺陷的。例如,通常可以動態分配所需空間,如例2.13所示。
例2.13 動態分配所需的空間
1 dest = (char *)malloc(strlen(source) + 1); 2 if (dest) { 3 strcpy(dest, source); 4 } else { 5 /* 處理錯誤 */ 6 ... 7 }
為了保證此代碼是安全的,必須對源字符串進行充分驗證[Wheeler 2004],例如,要確保該字符串不是過長的。在某些情況下,明顯不存在數組越界寫的可能。因此,簡單地替換所有的strcpy()調用或使得這種調用安全化可能并非特別有效。在其他情況下,用更加安全的替代函數取代strcpy()函數的調用,以消除由編譯器或分析工具生成的診斷消息的做法仍是可取的。
C標準的strncpy()函數經常被推薦作為一種替代strcpy()的函數。不幸的是,strncpy()容易產生空字符結尾錯誤和其他問題,因此不被認為是strcpy()函數的安全的替代函數。
OpenBSD。strlcpy()和strlcat()函數首先出現在OpenBSD 2.4中。這些函數以一種比相應的C標準函數更不容易出錯的方式復制和連接字符串。這些函數的原型如下:
size_t strlcpy(char *dst, const char *src, size_t size); size_t strlcat(char *dst, const char *src, size_t size);
strlcpy()函數從src復制一個以空字符結尾的字符串到dst(至多復制size個字符)。strlcat()函數在dst的末尾附加上以空字符結尾的src字符串(目標緩沖區內的字符數目最多不超過size個)。
為了幫助防止在數組邊界外寫入,strlcpy()和strlcat()函數接受目標字符串的全部長度作為size參數。
這兩個函數都保證對于所有非零長度的緩沖區,目標字符串是以空字符結尾的。
strlcpy()和strlcat()返回它們試圖創建的字符串的總長。對strlcpy()而言,這個值就是源字符串的長度;對strlcat()而言,這個值就是目標字符串(拼接前)和源字符串的長度之和。如果要檢測截斷,程序員需要驗證返回值是否小于size參數。如果結果字符串被截斷,那么程序員就可以獲得存儲整個字符串所需的實際字節數,然后重新分配內存并重新進行復制操作。
無論是strlcpy()或strlcat()都不會用空字符填充目標字符串(強制性的空結束符除外)。這就導致其性能與strcpy()接近,與strncpy()相比則好很多。
C11附錄K邊界檢查接口。在C11附錄K中,strcpy_s()和strcat_s()函數被定義為與strcpy()和strcat()函數非常接近的替代函數。strcpy_s()函數有一個額外的參數,用于給出目標數組的大小,來防止緩沖區溢出。
1 errno_t strcpy_s( 2 char * restrict s1, rsize_t s1max, const char * restrict s2 3 );
在沒有違反任何約束規則的情況下,strcpy_s()函數與strcpy()非常類似。strcpy_s()函數將源字符串中的字符復制到目標字符數組,直至遇到空結束符為止,并且復制的字符包含結尾的空字符。
strcpy_s()僅在源字符串可被完全復制到目標緩沖區且不引起目標緩沖區溢出的情況下才會調用成功。該函數執行成功時返回0,這意味著所有s2指向的字符串中請求的字符都填充在s1指向的數組內,并且在s1的結果是以空字符結尾的。否則,返回一個非零值。
strcpy_s()函數執行各種運行時約束。如果s1或s2是一個空指針、目標緩沖區的最大長度等于0,或大于RSIZE_MAX,或小于或等于源字符串的長度,或者,如果復制操作發生在重疊對象之間,就會發生運行時約束錯誤。目標字符串會被設置為空字符串,且函數返回一個非零值,以增加問題的能見度。
例2.14顯示了Open Watcom實現的strcpy_s()函數。運行時約束錯誤檢查都在其后加了注釋。
例2.14 Open Watcom的strcpy_s()函數實現
01 errno_t strcpy_s( 02 char * restrict s1, 03 rsize_t s1max, 04 const char * restrict s2 05 ) { 06 errno_t rc = -1; 07 const char *msg; 08 rsize_t s2len = strnlen_s(s2, s1max); 09 // 驗證運行時約束 10 if (nullptr_msg(msg, s1) && // s1 不是 NULL 11 nullptr_msg(msg, s2) && // s2 不是 NULL 12 maxsize_msg(msg, s1max) && // s1max <= RSIZE_MAX 13 zero_msg(msg, s1max) && // s1max != 0 14 a_gt_b_msg(msg, s2len, s1max - 1) && 15 // s1max > strnlen_s(s2, s1max) 16 overlap_msg(msg,s1,s1max,s2,s2len) // s1 與s2 不重疊 17 ) { 18 while (*s1++ = *s2++); 19 rc = 0; 20 } else { 21 // 運行時約束違反, 將目標字符串置空 22 if ((s1 != NULL) && (s1max > 0) && lte_rsizmax(s1max)) { 23 s1[0] = NULLCHAR; 24 } 25 // 現在調用處理程序 26 __rtct_fail(__func__, msg, NULL); 27 } 28 return(rc); 29 }
strcat_s()函數將源字符串中的字符追加到目標字符串的末尾,直至遇到空結束符為止,并且追加的字符包含結尾的空字符。源字符串存儲的初始字符將會覆蓋目標字符串原來結尾處的空字符。
strcat_s()調用成功時返回0。然而,如果源或目標指針為NULL或者目標緩沖區的最大長度為0或者比RSIZE_MAX更大,則目標字符串將被設為空串并且函數返回一個非零值。strcat_s()函數在目標字符串已滿或者沒有足夠的空間容納源字符串的情況下將會執行失敗。
在沒有正確指定目標緩沖區的最大長度的情況下,strcpy_s()和strcat_s()仍然可能會引起緩沖區溢出的問題。
動態分配函數。ISO/IEC TR 24731-2[ISO/IEC TR 24731-2:2010]描述了POSIX的strdup()函數,它也可以用于復制一個字符串。ISO/IEC TR 24731-2沒有定義任何替代strcat()的函數。strdup()函數接受一個指向一個字符串的指針,并返回一個指向新分配的復制品字符串的指針。這種內存必須通過把返回的指針傳遞給free()來收回。
替代函數總結。表2.5總結了本節中描述的字符串復制的一些替代函數。
表2.5 字符串復制函數
表2.6總結了一些本節中描述的strcat()的替代函數。TR 24731-2沒有定義strcat()的替代函數。
表2.6 字符串連接函數
- 垃圾回收的算法與實現
- 零基礎玩轉區塊鏈
- Responsive Web Design with HTML5 and CSS3
- Processing互動編程藝術
- Learning Python Design Patterns(Second Edition)
- Amazon S3 Cookbook
- EPLAN實戰設計
- Python:Master the Art of Design Patterns
- C語言程序設計
- RabbitMQ Essentials
- Clean Code in C#
- Webpack實戰:入門、進階與調優(第2版)
- Continuous Delivery and DevOps:A Quickstart Guide Second Edition
- SFML Game Development
- The Applied Data Science Workshop