摘要:釋放字符串內(nèi)存空間的時候,需要先釋放指針?biāo)赶虻膬?nèi)存空間,再釋放結(jié)構(gòu)體本身的內(nèi)存空間,效率同樣低下,而且這兩個操作順序不能顛倒。如果存了長度,就不會管你是否有,從頭開始讀字符串,一直讀長度為止即可。有些編譯器不支持?jǐn)?shù)組,可將其改成或均可。
baiyan
全部視頻:https://segmentfault.com/a/11...
源視頻地址:http://replay.xesv5.com/ll/24...
字符串的設(shè)計過程在C99的柔性數(shù)組標(biāo)準(zhǔn)未發(fā)布之前,我們?nèi)绻朐O(shè)計一個數(shù)據(jù)結(jié)構(gòu),存儲一個字符串,可以很容易地想出如下代碼:
struct string{ ... int len; //存長度(至于為什么存長度下文會講到) char* val; //存真正的字符串值 };
那么我們發(fā)現(xiàn),這樣做有如下缺點:
訪問字符串值的時候,需要先訪問結(jié)構(gòu)體,在訪問指針?biāo)赶虻膬?nèi)存空間,需要2次內(nèi)存訪問,效率低下。
釋放字符串內(nèi)存空間的時候,需要先釋放char *val指針?biāo)赶虻膬?nèi)存空間,再釋放結(jié)構(gòu)體本身的內(nèi)存空間,效率同樣低下,而且這兩個操作順序不能顛倒。
那么如何改進(jìn)呢?很容易想到,我們將字符串值和結(jié)構(gòu)體存儲在一片連續(xù)的內(nèi)存空間就可以了。這樣的話,訪問字符串與釋放字符串的內(nèi)存空間,均僅需1次內(nèi)存訪問,在C99柔性數(shù)組標(biāo)準(zhǔn)發(fā)布之前,改進(jìn)代碼的方式如下:
int main() { struct string{ int len; }; typedef struct string str; char *s = "he"; str *p = (str*)(malloc(sizeof(str) + strlen(s) + 1)); //分配足夠存下一個字符串的結(jié)構(gòu)體 p->len = strlen(s); memcpy(p + 1, s, strlen(s)); //將字符串拷貝到緊鄰結(jié)構(gòu)體的內(nèi)存處 }
小插曲:這個代碼的第一版,我還出現(xiàn)了一個指針加法的錯誤,見:關(guān)于memcpy一個字符串到緊鄰結(jié)構(gòu)體內(nèi)存空間處的疑問
我們利用gdb調(diào)試一下這段代碼:
首先我們應(yīng)該給這個結(jié)構(gòu)體分配4 + 2 + 1 = 7字節(jié)的內(nèi)存空間,但是由于內(nèi)存對齊的原因,最終分配了8字節(jié)大小的空間。
結(jié)構(gòu)體本身和len字段的地址均是0x602010,len字段的長度為4B,指針加上4B的len字段長度之后,就應(yīng)該是字符串he的起始地址,即0x602014,將其強轉(zhuǎn)為char *,發(fā)現(xiàn)正好就是我們存的字符串值"he"。注意不是p+4,而是p+1。因為p+4 = p+4*sizeof(指針p的類型)
由于這樣編寫代碼過于繁瑣,所以C99干脆制定一個標(biāo)準(zhǔn),使用柔性數(shù)組代替上述寫法。其實使用的計算方法和上面一段代碼是一樣的,只不過換了一種簡化的寫法而已,這段代碼最終內(nèi)存中的存儲情況如下:
PHP7中字符串的實現(xiàn)借助上文講到的字符串?dāng)?shù)據(jù)結(jié)構(gòu)設(shè)計思想,PHP中是這樣設(shè)計字符串的,它的結(jié)構(gòu)體叫做zend_string:
struct _zend_string { zend_refcounted_h gc; /*引用計數(shù),與垃圾回收相關(guān),暫不展開*/ zend_ulong h; /* 冗余的hash值,計算數(shù)組key的哈希值時避免重復(fù)計算*/ size_t len; /* 長度 */ char val[1]; /* 柔性數(shù)組,真正存放字符串值 */ };
第一個問題:為什么要存長度len?不存長度,直接和C語言一樣通過字符串的"