<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      PHP7內(nèi)核(八):深入理解字符串的實現(xiàn)

      在前面大致預覽了常用變量的結(jié)構(gòu)之后,我們今天來仔細的剖析一下字符串的具體實現(xiàn)。

      一、字符串的結(jié)構(gòu)

      struct _zend_string {
      	zend_refcounted_h gc;       /* 字符串類別及引用計數(shù) */
      	zend_ulong        h;        /* 字符串的哈希值 */
      	size_t            len;      /* 字符串的長度 */
      	char              val[1];   /* 柔性數(shù)組,字符串存儲位置 */
      };
      

      zend_refcounted_h對應的結(jié)構(gòu)體:

      typedef struct _zend_refcounted_h {
      	uint32_t         refcount;			/* 引用計數(shù) */
      	union {
      		struct {
      			ZEND_ENDIAN_LOHI_3(
      				zend_uchar    type,     
      				zend_uchar    flags,    /* 字符串的類型 */
      				uint16_t      gc_info   /* 垃圾回收信息 */
      			)
      		} v;
      		uint32_t type_info;
      	} u;
      } zend_refcounted_h;
      

      image

      下面我們來了解一下具體每個成員的作用:

      • gc:就是_zend_refcounted_h結(jié)構(gòu)體,主要作用是引用計數(shù)以及標記變量的類別。
      • h:字符串的哈希值,在字符串被用來當數(shù)組的key時才初始化,這樣如果同一個字符串被多次用來做key,就不會重復計算了。
      • val:這里的char[1]并不意味著只存儲1位,char[1]被稱為柔性數(shù)組,下面來了解一下PHP在字符串內(nèi)存分配時做了什么。
      static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)
      {
      	zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
          ......
      }
      

      宏替換后:

      static zend_always_inline zend_string *zend_string_alloc(size_t len, int persistent)
      {
      	zend_string *ret = (zend_string *)pemalloc(ZEND_MM_ALIGNED_SIZE(XtOffsetOf(zend_string, val) + len + 1), persistent);
          ......
      }
      

      示例中的代碼XtOffsetOf(zend_string, val)表示計算出zend_string結(jié)構(gòu)體的大小,而len就是要分配字符串的長度,最后的+1是留給結(jié)束字符\0的。也就是說,分配內(nèi)存時不僅僅分配結(jié)構(gòu)體大小的內(nèi)存,還要顧及到長度不可控的val,這樣不僅柔性的分配了內(nèi)存,還使它與其他成員存儲在同一塊連續(xù)的空間中,在分配、釋放內(nèi)存時可以把struct統(tǒng)一處理。

      • len:字符串的長度,避免重復計算浪費時間,典型的空間換時間做法。

      二、字符串的二進制安全

      學習過C語言的應該知道,字符串中除了最后一個字符外不允許含有\0,否則會被認為是字符串的結(jié)束字符,這就導致了C語言的字符串有很多的限制,比如不存儲圖片、文件等二進制數(shù)據(jù)。但是PHP就沒有這樣的限制,它的字符串可以存儲二進制數(shù)據(jù),并不會出現(xiàn)任何報錯,而PHP的這種能力就叫做字符串的二進制安全。

      C語言代碼如下:

      main() {
          char a[] = "aaa\0b";    /* 含有\(zhòng)0的字符串 */
          printf("%d\n", strlen(a));  /* 長度為3,\0后的b被忽略 */
      }
      

      PHP代碼:

      <?php
          $a = "aaa\0b";
          echo strlen($a);    //輸出5
      ?>
      

      但是PHP不是C語言寫的嗎?為什么PHP不會報錯?我們再來回顧一下zend_string結(jié)構(gòu)體,還記得成員變量len嗎?它是實現(xiàn)二進制安全的關(guān)鍵,我們不需要像C一樣通過\0來判定字符串是否被讀取完成,而是通過長度len來判斷,這樣就保證了字符串的二進制安全。

      三、zend_string API

      在了解了zend_string結(jié)構(gòu)之后,我們來了解一下用來操作zend_string的函數(shù)集合。

      函數(shù) 作用
      zend_interned_strings_init 初始化內(nèi)部字符串存儲哈希表,并把PHP的關(guān)鍵字等字符串信息寫進去
      zend_new_interned_string 把一個zend_string寫入CG(interned_strings)哈希表中
      zend_interned_strings_snapshot 將CG(interned_strings)哈希表中的字符串標記為永久字符串,這里標記的只有PHP關(guān)鍵字、內(nèi)部函數(shù)名、內(nèi)部方法名等
      zend_interned_strings_restore 銷毀CG(interned_strings)哈希表中類型為非永久字符串的值,在php_request_shutdown階段釋放
      zend_interned_strings_dtor 銷毀整個CG(interned_strings)哈希表,在php_module_shutdown階段釋放
      zend_string_hash_val 得到字符串的哈希值,沒有則實時計算
      zend_string_forget_hash_val 將字符串的哈希值置為0
      zend_string_refcount 讀取字符串的引用計數(shù)
      zend_string_addref 引用計數(shù)+1
      zend_string_delref 引用計數(shù)-1
      zend_string_alloc 分配內(nèi)存及初始化字符串的值
      zend_string_init 初始化字符串并在最后追加\0
      zend_string_cop 使用引用計數(shù)方式復制字符串
      zend_string_dup 直接復制一個字符串
      zend_string_extend 擴容到len,保留原來的值
      zend_string_truncate 截斷到len,保留開頭到len的值
      zend_string_free 釋放字符串內(nèi)存
      zend_string_release GC引用遞減,直到為0時釋放內(nèi)存
      zend_string_equals 普通判等
      zend_string_equals_ci 基于二進制安全,兩個zend_string類型字符串判等
      zend_string_equals_literal_ci 基于二進制安全,zend_string類型和char*字符串判等
      zend_inline_hash_func 計算字符串的哈希值
      zend_intern_known_strings 往zend_intern_known_strings全局數(shù)組寫入str

      下面挑幾個函數(shù)來介紹一下。

      3.1、zend_string_init函數(shù)

      zend_string_init函數(shù)主要負責把一個普通的字符串轉(zhuǎn)化為zend_string結(jié)構(gòu)體。

      static zend_always_inline zend_string *zend_string_init(const char *str, size_t len, int persistent)
      {
      	zend_string *ret = zend_string_alloc(len, persistent);
      
      	memcpy(ZSTR_VAL(ret), str, len);
      	ZSTR_VAL(ret)[len] = '\0';
      	return ret;
      }
      
      • 申請一塊連續(xù)的內(nèi)存,這個在上文中已經(jīng)提到,申請的內(nèi)存大小是zend_string結(jié)構(gòu)體大小+字符串長度+1。
      • 指針偏移到val位置,開始字符串拷貝。
      • 在zend_string.val結(jié)尾追加\0

      3.2、zend_string_extend函數(shù)

      該函數(shù)主要用于對字符串的擴容,注意這里擴容不會改變原來保存的值,只是把長度擴大到len。

      static zend_always_inline zend_string *zend_string_extend(zend_string *s, size_t len, int persistent)
      {
      	zend_string *ret;
      
      	ZEND_ASSERT(len >= ZSTR_LEN(s));
      	if (!ZSTR_IS_INTERNED(s)) {
      		if (EXPECTED(GC_REFCOUNT(s) == 1)) {
      			ret = (zend_string *)perealloc(s, ZEND_MM_ALIGNED_SIZE(_ZSTR_STRUCT_SIZE(len)), persistent);
      			ZSTR_LEN(ret) = len;
      			zend_string_forget_hash_val(ret);
      			return ret;
      		} else {
      			GC_REFCOUNT(s)--;
      		}
      	}
      	ret = zend_string_alloc(len, persistent);
      	memcpy(ZSTR_VAL(ret), ZSTR_VAL(s), ZSTR_LEN(s) + 1);
      	return ret;
      }
      
      • 如果不是內(nèi)部字符串并且引用計數(shù)為1時,直接調(diào)用perealloc分配內(nèi)存。
      • 如果字符串的引用計數(shù)大于1或者是內(nèi)部字符串時,就不能在原來的基礎(chǔ)上擴容了,先通過zend_string_alloc申請一塊新內(nèi)存,讓后將舊內(nèi)容拷貝到新內(nèi)存中。

      3.3、zend_string_equals_ci函數(shù)

      主要基于二進制安全對兩個字符串進行判等,我們來看下PHP是怎么比較兩個字符串的。

      #define zend_string_equals_ci(s1, s2) \
      	(ZSTR_LEN(s1) == ZSTR_LEN(s2) && !zend_binary_strcasecmp(ZSTR_VAL(s1), ZSTR_LEN(s1), ZSTR_VAL(s2), ZSTR_LEN(s2)))
      
      
      • 先比較兩個字符串的長度是否相等,注意這里是通過zend_string中的len來比較的。
      • zend_binary_strcasecmp函數(shù)在長度比較完成后,進行逐個字符進行比較。先遍歷整個字符串數(shù)組,取出每個字符,轉(zhuǎn)換為ASC碼進行判等,如果不等則返回差值。循環(huán)完了還沒發(fā)現(xiàn)差異的話就返回兩者的長度差,如果長度相等就返回0。感覺這里做的有點多余,參數(shù)傳進來之前就已經(jīng)做了長度判等了。
      ZEND_API int ZEND_FASTCALL zend_binary_strcasecmp(const char *s1, size_t len1, const char *s2, size_t len2) /* {{{ */
      {
      	size_t len;
      	int c1, c2;
      
      	if (s1 == s2) {
      		return 0;
      	}
      
      	len = MIN(len1, len2);
      	while (len--) {
      		c1 = zend_tolower_ascii(*(unsigned char *)s1++);
      		c2 = zend_tolower_ascii(*(unsigned char *)s2++);
      		if (c1 != c2) {
      			return c1 - c2;
      		}
      	}
      
      	return (int)(len1 - len2);
      }
      

      感興趣的同學可以到源碼中查看。

      四、參考文獻

      • 《PHP7底層設(shè)計與源碼實現(xiàn)》
      • 《PHP7內(nèi)核剖析》
      posted @ 2018-09-21 20:03  MARIOOW  閱讀(2134)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 小金县| 蜜芽久久人人超碰爱香蕉| 亚洲欧洲一区二区免费| 久久天天躁狠狠躁夜夜婷| 顺昌县| 国产午夜福利精品视频| 成人国产精品日本在线观看| 一本色道婷婷久久欧美| 国产精品99区一区二区三| 无遮挡粉嫩小泬久久久久久久| 国产成人精品亚洲日本片| 国语偷拍视频一区二区三区| 亚洲精品国产自在现线最新| 亚洲av成人区国产精品| 亚洲高清WWW色好看美女| 依依成人精品视频在线观看| 成人3d动漫一区二区三区| 国产在线一区二区不卡| 亚洲一区二区三区蜜桃臀| 定襄县| 国产精品一二三区蜜臀av| 国产超碰无码最新上传| 人妻少妇精品视频二区| 国产欧美日韩亚洲一区二区三区| 亚洲欧美综合精品成人导航| 欧美在线观看www| 国产一区二区三区尤物视频 | 免费激情网址| 亚洲国产区男人本色vr| 国产av不卡一区二区| 平昌县| 国语精品自产拍在线观看网站| 国产精品免费AⅤ片在线观看| 人妻少妇久久中文字幕| 高中女无套中出17p| 亚洲 丝袜 另类 校园 欧美 | 国产精品美女www爽爽爽视频| 天堂mv在线mv免费mv香蕉| 国产亚洲精品在av| 亚洲精品不卡无码福利在线观看| 午夜欧美精品久久久久久久|