【PHP7源碼學習】2019-03-13 PHP字符串筆記

baiyansegmentfault

所有視頻:https://segmentfault.com/a/11...數組

源視頻地址:http://replay.xesv5.com/ll/24...安全

字符串的設計過程

  • 在C99的柔性數組標準未發佈以前,咱們若是想設計一個數據結構,存儲一個字符串,能夠很容易地想出以下代碼:
struct string{
        ...
        int len;   //存長度(至於爲何存長度下文會講到)
        char* val; //存真正的字符串值
    };
  • 那麼咱們發現,這樣作有以下缺點:數據結構

    • 訪問字符串值的時候,須要先訪問結構體,在訪問指針所指向的內存空間,須要2次內存訪問,效率低下。
    • 釋放字符串內存空間的時候,須要先釋放char *val指針所指向的內存空間,再釋放結構體自己的內存空間,效率一樣低下,並且這兩個操做順序不能顛倒。
    • 那麼如何改進呢?很容易想到,咱們將字符串值和結構體存儲在一片連續的內存空間就能夠了。這樣的話,訪問字符串與釋放字符串的內存空間,均僅需1次內存訪問,在C99柔性數組標準發佈以前,改進代碼的方式以下:
int main() {

    struct string{
        int len;
    };

    typedef struct string str;

    char *s = "he";
    str *p = (str*)(malloc(sizeof(str) + strlen(s) + 1)); //分配足夠存下一個字符串的結構體
    p->len = strlen(s);
    memcpy(p + 1, s, strlen(s)); //將字符串拷貝到緊鄰結構體的內存處

}

  • 首先咱們應該給這個結構體分配4 + 2 + 1 = 7字節的內存空間,可是因爲內存對齊的緣由,最終分配了8字節大小的空間。
  • 結構體自己和len字段的地址均是0x602010,len字段的長度爲4B,指針加上4B的len字段長度以後,就應該是字符串he的起始地址,即0x602014,將其強轉爲char *,發現正好就是咱們存的字符串值"he"。注意不是p+4,而是p+1。由於p+4 = p+4*sizeof(指針p的類型)
  • 因爲這樣編寫代碼過於繁瑣,因此C99乾脆制定一個標準,使用柔性數組代替上述寫法。其實使用的計算方法和上面一段代碼是同樣的,只不過換了一種簡化的寫法而已,這段代碼最終內存中的存儲狀況以下:

PHP7中字符串的實現

  • 藉助上文講到的字符串數據結構設計思想,PHP中是這樣設計字符串的,它的結構體叫作zend_string:
struct _zend_string {
    zend_refcounted_h gc;         /*引用計數,與垃圾回收相關,暫不展開*/
    zend_ulong        h;          /* 冗餘的hash值,計算數組key的哈希值時避免重複計算*/
    size_t            len;        /* 長度 */
    char              val[1];     /* 柔性數組,真正存放字符串值 */
};
  • 第一個問題:爲何要存長度len?不存長度,直接和C語言同樣經過字符串的'\0'來判斷字符串結束不行嗎?不行。這裏有一個二進制安全的問題。spa

    • 二進制安全:寫入的數據和讀出來的數據徹底相同,就是二進制安全的。
    • 假設你寫入了一個字符串的內容爲:hello\0world,按照C語言的讀取字符串的方法就會斷定\0是字符串結束的標誌,讀出來就是hello,這樣讀出來的數據就和寫入的數據不一致,就是非二進制安全的。
    • 若是存了長度,就不會管你是否有\0,從頭開始讀字符串,一直讀len長度爲止便可。
  • 第二個問題:最後一個字段改爲char val[0]能夠嗎?能夠。寫成char val[1]是出於可移植性的考慮。有些編譯器不支持[0]數組,可將其改爲[]或[1]都可。
相關文章
相關標籤/搜索