練習36:更安全的字串
譯者:飛龍
我已經在練習26中,構建devpkg
的時候介紹了Better String庫。這個練習讓你從現在開始熟悉bstring
庫,並且明白C風格字串為什麼十分糟糕。之後你需要修改liblcthw
的程式碼來使用bstring
。
為什麼C風格字串十分糟糕
當人們談論C的問題時,“字串”的概念永遠是首要缺陷之一。你已經用過它們,並且我也談論過它們的種種缺陷,但是對為什麼C字串擁有缺陷,以及為什麼一直是這樣沒有明確的解釋。我會試著現在做出解釋,部分原因是C風格字串經過數十年的使用,有足夠的證據表明它們是個非常糟糕的東西。
對於給定的任何C風格字串,都不可能驗證它是否有效。
-
以
` `
結尾的C字串是有效的。 -
任何處理無效C字串的迴圈都是無限的(或者造成緩衝區溢位)。
-
C字串沒有確定的長度,所以檢查它們的唯一方法就是遍歷它來觀察迴圈是否正確終止。
-
所以,不通過有限的迴圈就不可能驗證C字串。
這個邏輯非常簡單。你不能編寫一個迴圈來驗證C字串是否有效,因為無效的字串導致迴圈永遠不會停止。就是這樣,唯一的解決方案就是包含大小。一旦你知道了大小,你可以避免無限迴圈問題。如果你觀察練習27中我向你展示的兩個函式:
譯者注:檢驗C風格字串是否有效等價於“停機問題”,這是一個非常著名的不可解問題。
void copy(char to[], char from[])
{
int i = 0;
// while loop will not end if from isn`t ` ` terminated
while((to[i] = from[i]) != ` `) {
++i;
}
}
int safercopy(int from_len, char *from, int to_len, char *to)
{
int i = 0;
int max = from_len > to_len - 1 ? to_len - 1 : from_len;
// to_len must have at least 1 byte
if(from_len < 0 || to_len <= 0) return -1;
for(i = 0; i < max; i++) {
to[i] = from[i];
}
to[to_len - 1] = ` `;
return i;
}
想象你想要向copy
函式新增檢查來確保from
字串有效。你該怎麼做呢?你編寫了一個迴圈來檢查字串是否已` `
結尾。哦,等一下,如果字串不以` `
結尾,那它怎麼讓迴圈停下?不可能停下,所以無解。
無論你怎麼做,你都不能在不知道字串長度的情況下檢查C字串的有效性,這裡safercopy
包含了程度。這個函式沒有相同的問題,因為他的迴圈一定會中止,即使你傳入了錯誤的大小,大小也是有限的。
譯者注:但是問題來了,對於一個C字串,你怎麼獲取其大小?你需要在這個函式之前呼叫
strlen
,又是一個無限迴圈問題。
於是,bstring
庫所做的事情就是建立一個結構體,它總是包含字串長度。由於這個長度對於bstring
來說總是可訪問的,它上面的所有操作都會更安全。迴圈是有限的,內容也是有效的,並且這個主要的缺陷也不存在了。BString庫也帶有大量所需的字串操作,比如分割、格式化、搜尋,並且大多數都會正確並安全地執行。
bstring
中也可能有缺陷,但是經過這麼長時間,可能性已經很低了。glibc
中也有缺陷,所以你讓程式設計師怎麼做才好呢?
使用 bstrlib
有很多改進後的字串庫,但是我最喜歡bstrlib
,因為它只有一個程式集,並且具有大多數所需的字串功能。你已經在使用它了,所以這個練習中你需要從Better String獲取兩個檔案,bstrlib.c
和bstrlib.h
。
下面是我在liblcthw
專案目錄裡所做的事情:
$ mkdir bstrlib
$ cd bstrlib/
$ unzip ~/Downloads/bstrlib-05122010.zip
Archive: /Users/zedshaw/Downloads/bstrlib-05122010.zip
...
$ ls
bsafe.c bstraux.c bstrlib.h bstrwrap.h license.txt test.cpp
bsafe.h bstraux.h bstrlib.txt cpptest.cpp porting.txt testaux.c
bstest.c bstrlib.c bstrwrap.cpp gpl.txt security.txt
$ mv bstrlib.h bstrlib.c ../src/lcthw/
$ cd ../
$ rm -rf bstrlib
# make the edits
$ vim src/lcthw/bstrlib.c
$ make clean all
...
$
在第14行你可以看到,我編輯了bstrlib.c
檔案,來將它移動到新的位置,並且修復OSX上的bug。下面是差異:
25c25
< #include "bstrlib.h"
---
> #include <lcthw/bstrlib.h>
2759c2759
< #ifdef __GNUC__
---
> #if defined(__GNUC__) && !defined(__APPLE__)
我把包含修改為<lcthw/bstrlib.h>
,然後修復2759行ifdef
的問題。
學習使用該庫
這個練習很短,只是讓你準備好剩餘的練習,它們會用到這個庫。接下來兩個聯絡中,我會使用bstrlib.c
來建立Hashmap`資料結構。
你現在應該閱讀標頭檔案和實現,之後編寫tests/bstr_tests.c
來測試下列函式,來熟悉這個庫:
bfromcstr
從C風格字串中建立一個bstring
。
blk2bstr
與上面相同,但是可以提供緩衝區長度。
bstrcpy
複製bstring
。
bassign
將一個bstring
賦值為另一個。
bassigncstr
將bsting
的內容設定為C字串的內容。
bassignblk
將bsting
的內容設定為C字串的內容,但是可以提供長度。
bdestroy
銷燬bstring
。
bconcat
在一個bstring
末尾連線另一個。
bstricmp
比較兩個bstring
,返回值與strcmp
相同。
biseq
檢查兩個bstring
是否相等。
binstr
判斷一個bstring
是否被包含於另一個。
bfindreplace
在一個bstring
中尋找另一個,並且將其替換為別的。
bsplit
將bstring
分割為bstrList
。
bformat
執行字串格式化,十分便利。
blength
獲取bstring
的長度。
bdata
獲取bstring
的資料。
bchar
獲得bstring
中的字元。
你的測試應該覆蓋到所有這些操作,以及你從標頭檔案中發現的更多有趣的東西。在valgrind
下執行測試,確保記憶體使用正確。