練習(xí)36:更安全的字符串
譯者:飛龍
我已經(jīng)在練習(xí)26中绑莺,構(gòu)建devpkg
的時(shí)候介紹了Better String庫(kù)演熟。這個(gè)練習(xí)讓你從現(xiàn)在開(kāi)始熟悉bstring
庫(kù),并且明白C風(fēng)格字符串為什么十分糟糕趁猴。之后你需要修改liblcthw
的代碼來(lái)使用bstring
。
為什么C風(fēng)格字符串十分糟糕
當(dāng)人們談?wù)揅的問(wèn)題時(shí)富纸,“字符串”的概念永遠(yuǎn)是首要缺陷之一集畅。你已經(jīng)用過(guò)它們,并且我也談?wù)撨^(guò)它們的種種缺陷,但是對(duì)為什么C字符串擁有缺陷枯夜,以及為什么一直是這樣沒(méi)有明確的解釋弯汰。我會(huì)試著現(xiàn)在做出解釋,部分原因是C風(fēng)格字符串經(jīng)過(guò)數(shù)十年的使用湖雹,有足夠的證據(jù)表明它們是個(gè)非常糟糕的東西咏闪。
對(duì)于給定的任何C風(fēng)格字符串,都不可能驗(yàn)證它是否有效摔吏。
- 以
'\0'
結(jié)尾的C字符串是有效的鸽嫂。 - 任何處理無(wú)效C字符串的循環(huán)都是無(wú)限的(或者造成緩沖區(qū)溢出)。
- C字符串沒(méi)有確定的長(zhǎng)度征讲,所以檢查它們的唯一方法就是遍歷它來(lái)觀察循環(huán)是否正確終止据某。
- 所以,不通過(guò)有限的循環(huán)就不可能驗(yàn)證C字符串诗箍。
這個(gè)邏輯非常簡(jiǎn)單癣籽。你不能編寫(xiě)一個(gè)循環(huán)來(lái)驗(yàn)證C字符串是否有效,因?yàn)闊o(wú)效的字符串導(dǎo)致循環(huán)永遠(yuǎn)不會(huì)停止滤祖。就是這樣筷狼,唯一的解決方案就是包含大小。一旦你知道了大小匠童,你可以避免無(wú)限循環(huán)問(wèn)題桑逝。如果你觀察練習(xí)27中我向你展示的兩個(gè)函數(shù):
譯者注:檢驗(yàn)C風(fēng)格字符串是否有效等價(jià)于“停機(jī)問(wèn)題”,這是一個(gè)非常著名的不可解問(wèn)題俏让。
void copy(char to[], char from[])
{
int i = 0;
// while loop will not end if from isn't '\0' terminated
while((to[i] = from[i]) != '\0') {
++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] = '\0';
return i;
}
想象你想要向copy
函數(shù)添加檢查來(lái)確保from
字符串有效楞遏。你該怎么做呢?你編寫(xiě)了一個(gè)循環(huán)來(lái)檢查字符串是否已'\0'
結(jié)尾首昔。哦寡喝,等一下,如果字符串不以'\0'
結(jié)尾勒奇,那它怎么讓循環(huán)停下预鬓?不可能停下,所以無(wú)解赊颠。
無(wú)論你怎么做格二,你都不能在不知道字符串長(zhǎng)度的情況下檢查C字符串的有效性,這里safercopy
包含了程度竣蹦。這個(gè)函數(shù)沒(méi)有相同的問(wèn)題顶猜,因?yàn)樗难h(huán)一定會(huì)中止,即使你傳入了錯(cuò)誤的大小痘括,大小也是有限的长窄。
譯者注:但是問(wèn)題來(lái)了滔吠,對(duì)于一個(gè)C字符串,你怎么獲取其大心尤铡疮绷?你需要在這個(gè)函數(shù)之前調(diào)用
strlen
,又是一個(gè)無(wú)限循環(huán)問(wèn)題嚣潜。
于是冬骚,bstring
庫(kù)所做的事情就是創(chuàng)建一個(gè)結(jié)構(gòu)體,它總是包含字符串長(zhǎng)度懂算。由于這個(gè)長(zhǎng)度對(duì)于bstring
來(lái)說(shuō)總是可訪問(wèn)的唉韭,它上面的所有操作都會(huì)更安全。循環(huán)是有限的犯犁,內(nèi)容也是有效的属愤,并且這個(gè)主要的缺陷也不存在了。BString庫(kù)也帶有大量所需的字串操作酸役,比如分割住诸、格式化、搜索涣澡,并且大多數(shù)都會(huì)正確并安全地執(zhí)行贱呐。
bstring
中也可能有缺陷,但是經(jīng)過(guò)這么長(zhǎng)時(shí)間入桂,可能性已經(jīng)很低了奄薇。glibc
中也有缺陷,所以你讓程序員怎么做才好呢抗愁?
使用 bstrlib
有很多改進(jìn)后的字符串庫(kù)馁蒂,但是我最喜歡bstrlib
,因?yàn)樗挥幸粋€(gè)程序集蜘腌,并且具有大多數(shù)所需的字符串功能沫屡。你已經(jīng)在使用它了,所以這個(gè)練習(xí)中你需要從Better String獲取兩個(gè)文件撮珠,bstrlib.c
和bstrlib.h
沮脖。
下面是我在liblcthw
項(xiàng)目目錄里所做的事情:
$ 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
文件芯急,來(lái)將它移動(dòng)到新的位置勺届,并且修復(fù)OSX上的bug。下面是差異:
25c25
< #include "bstrlib.h"
---
> #include <lcthw/bstrlib.h>
2759c2759
< #ifdef __GNUC__
---
> #if defined(__GNUC__) && !defined(__APPLE__)
我把包含修改為<lcthw/bstrlib.h>
娶耍,然后修復(fù)2759行ifdef
的問(wèn)題免姿。
學(xué)習(xí)使用該庫(kù)
這個(gè)練習(xí)很短,只是讓你準(zhǔn)備好剩余的練習(xí)伺绽,它們會(huì)用到這個(gè)庫(kù)养泡。接下來(lái)兩個(gè)聯(lián)系中嗜湃,我會(huì)使用bstrlib.c
來(lái)創(chuàng)建Hashmap`數(shù)據(jù)結(jié)構(gòu)奈应。
你現(xiàn)在應(yīng)該閱讀頭文件和實(shí)現(xiàn)澜掩,之后編寫(xiě)tests/bstr_tests.c
來(lái)測(cè)試下列函數(shù),來(lái)熟悉這個(gè)庫(kù):
bfromcstr
從C風(fēng)格字符串中創(chuàng)建一個(gè)bstring
杖挣。
blk2bstr
與上面相同肩榕,但是可以提供緩沖區(qū)長(zhǎng)度。
bstrcpy
復(fù)制bstring
惩妇。
bassign
將一個(gè)bstring
賦值為另一個(gè)株汉。
bassigncstr
將bsting
的內(nèi)容設(shè)置為C字符串的內(nèi)容。
bassignblk
將bsting
的內(nèi)容設(shè)置為C字符串的內(nèi)容歌殃,但是可以提供長(zhǎng)度乔妈。
bdestroy
銷(xiāo)毀bstring
。
bconcat
在一個(gè)bstring
末尾連接另一個(gè)氓皱。
bstricmp
比較兩個(gè)bstring
路召,返回值與strcmp
相同。
biseq
檢查兩個(gè)bstring
是否相等波材。
binstr
判斷一個(gè)bstring
是否被包含于另一個(gè)股淡。
bfindreplace
在一個(gè)bstring
中尋找另一個(gè),并且將其替換為別的廷区。
bsplit
將bstring
分割為bstrList
唯灵。
bformat
執(zhí)行字符串格式化,十分便利隙轻。
blength
獲取bstring
的長(zhǎng)度埠帕。
bdata
獲取bstring
的數(shù)據(jù)。
bchar
獲得bstring
中的字符玖绿。
你的測(cè)試應(yīng)該覆蓋到所有這些操作搞监,以及你從頭文件中發(fā)現(xiàn)的更多有趣的東西。在valgrind
下運(yùn)行測(cè)試镰矿,確保內(nèi)存使用正確琐驴。