真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

php的字符串如何管理zend_string

這篇文章運(yùn)用簡單易懂的例子給大家介紹php的字符串如何管理 zend_string,代碼非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。

成都創(chuàng)新互聯(lián)公司專注于泊頭網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供泊頭營銷型網(wǎng)站建設(shè),泊頭網(wǎng)站制作、泊頭網(wǎng)頁設(shè)計(jì)、泊頭網(wǎng)站官網(wǎng)定制、微信小程序開發(fā)服務(wù),打造泊頭網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供泊頭網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

                                               

字符串管理:zend_string

任何程序都需要管理字符串。在這里,我們將詳細(xì)介紹適合 PHP 需求的自定義解決方案:zend_string。每次 PHP 需要使用字符串時(shí),都會(huì)使用 zend_string 結(jié)構(gòu)。該結(jié)構(gòu)僅僅是 C 語言的 char * 字符串類型的簡單精簡包裝。

它添加了內(nèi)存管理的功能,所以同一字符串可以在多個(gè)地方共享,而無需重復(fù)。另外,一些字符串是“內(nèi)部的”,即“持久的”分配,并通過內(nèi)存管理特殊管理,以便它們不會(huì)在多個(gè)請(qǐng)求中被銷毀。之后,那些從Zend 內(nèi)存管理獲得永久分配。

這里是簡單的zend_string結(jié)構(gòu):

struct _zend_string {
        zend_refcounted_h gc;
        zend_ulong        h;
        size_t            len;
        char              val[1];
};

如你所見,該結(jié)構(gòu)嵌入了一個(gè) zend_refcounted_h 標(biāo)頭。這個(gè)是內(nèi)存管理和引用需要用到的。 由于該字符串很有可能作為哈希表檢查的關(guān)鍵字,因此它在 h字段中嵌入了其哈希值。這是無符號(hào)長整型 zend_ulong。僅在需要對(duì) zend_string 進(jìn)行哈希處理時(shí)會(huì)用到,特別是和哈希表:zend_array一起用時(shí)。這很有可能。

如你所知,字符串知道其長度為 len 字段,以支持“二進(jìn)制字符串。二進(jìn)制字符串是嵌入一個(gè)或多個(gè) NUL 字符(\0)的字符串。當(dāng)傳遞給庫函數(shù),那些字符串會(huì)被截?cái)啵駝t無法正確計(jì)算其長度。所以在 zend_string 中,字符串的長度總是已知的。請(qǐng)注意,該長度計(jì)算的 ASCII 字符(字節(jié)),不計(jì)算最后的NUL,而是計(jì)算最終的中間的 NUL。例如,字符串 “foo” 在 zend_string 中存儲(chǔ)為 “foo\0”,且它的長度為3。另外,字符串 “foo\0bar” 將存儲(chǔ)為 “foo\0bar\0”,且其長度為7。

最終,該字符存儲(chǔ)在 char[1]。這不是 char *,而是 char[1]。為什么?這是一種稱為 “C struct hack” 的內(nèi)存優(yōu)化(你可以使用帶有這些術(shù)語的搜索引擎)?;旧?,它允許引擎為 zend_string 結(jié)構(gòu)和要存儲(chǔ)的字符分配空間,作為一個(gè)單獨(dú)的 C 指針。這優(yōu)化了內(nèi)存,因?yàn)閮?nèi)存訪問將是一個(gè)連續(xù)分配的塊,而不是兩個(gè)分散的塊(一個(gè)用于存儲(chǔ) zend_string *,另一個(gè)用于存儲(chǔ) char *)。

必須記住這種 struct hack,由于內(nèi)存布局看起來像 C 字符位于 C zend_string 結(jié)構(gòu)的末尾,因此當(dāng)使用 C 調(diào)試器(或調(diào)試字符串)時(shí)可能會(huì)感覺到/看到過。該 hack 是完全由 API 管理,當(dāng)你操作 zend_string結(jié)構(gòu)時(shí)會(huì)用到。

php的字符串如何管理 zend_string

使用 zend_string API

簡單用例

像 Zvals,你不需要手動(dòng)操作 zend_string 內(nèi)部字段,而總是為此使用宏。還存在觸發(fā)字符串操作的宏。這并不是函數(shù),而是宏,都存儲(chǔ)在必需的 Zend/zend_string.h 頭文件:

zend_string *str;

str = zend_string_init("foo", strlen("foo"), 0);
php_printf("This is my string: %s\n", ZSTR_VAL(str));
php_printf("It is %zd char long\n", ZSTR_LEN(str));

zend_string_release(str);

上面簡單的例子為你展示了基本的字符串管理。應(yīng)該為 zend_string_init() 函數(shù)(實(shí)際上是宏,但先讓我們忽略它)給出完整的 char * C 字符串和它的長度。類型為 int 的最后一個(gè)參數(shù)應(yīng)該為 0 或 1。如果傳遞0,則要求引擎通過 Zend 內(nèi)存管理使用請(qǐng)求綁定的堆分配。這種分配在當(dāng)前請(qǐng)求結(jié)束后時(shí)銷毀。如果你不這么做,則在調(diào)試版本中,引擎會(huì)提醒你內(nèi)存泄漏。如果傳遞1,則要求了所謂的“持久”分配,引擎將使用傳統(tǒng)的 C malloc() 調(diào)用,并且不會(huì)以任何方式追蹤內(nèi)存分配。

注意

如果你需要更多有關(guān)內(nèi)存管理的信息,你可以閱讀專用章節(jié)。

然后,我們來顯示字符串。我們使用 ZSTR_VAL() 宏訪問字符數(shù)組。ZSTR_LEN() 允許訪問長度信息。zend_string 相關(guān)宏都以 ZSTR_**() 開始,注意和 Z_STR**() 宏不一樣。

注意

長度使用 size_t 類型存儲(chǔ),為了顯示它,printf() 必須使用 “%zd”。你應(yīng)該總是使用正確的printf()格式。否則可能會(huì)導(dǎo)致應(yīng)用程序崩潰或創(chuàng)建安全問題否則可能會(huì)導(dǎo)致內(nèi)存泄漏和。有關(guān) printf() 格式的詳細(xì)信息,請(qǐng)?jiān)L問此鏈接

最后,我們使用 zend_string_release()釋放字符串。該釋放是強(qiáng)制的。這與內(nèi)存管理有關(guān)?!搬尫拧笔且粋€(gè)簡單的操作:字符串的引用計(jì)數(shù)遞減,如果減到0,API會(huì)為你釋放字符串。如果忘記釋放字符串,則很可能造成內(nèi)存泄漏。

注意

在 C 語言中,你必須總是考慮內(nèi)存管理。如果你分配——不管是直接使用 malloc(),或者使用能為你這樣做的 API,在某些時(shí)候你必須使用 free()。否則可能會(huì)導(dǎo)致內(nèi)存泄漏,并轉(zhuǎn)換為任何人都不能安全使用的糟糕設(shè)計(jì)程序。

玩轉(zhuǎn) hash

如果你需要訪問哈希值,可使用 ZSTR_H()。但創(chuàng)建 zend_string 時(shí),不會(huì)自動(dòng)計(jì)算其哈希值。而當(dāng)將該字符串與 HashTable API 一起使用時(shí),它將為你完成。如果你強(qiáng)制立即計(jì)算哈希值,可使用 ZSTR_HASH()zend_string_hash_val()。當(dāng)哈希值被計(jì)算出來,它會(huì)被保存起來并且不再被計(jì)算。無論如何,你必須使用  zend_string_forget_hash_val() 重新計(jì)算——因?yàn)槟愀淖兞俗址闹担?/p>

zend_string *str;

str = zend_string_init("foo", strlen("foo"), 0);
php_printf("This is my string: %s\n", ZSTR_VAL(str));
php_printf("It is %zd char long\n", ZSTR_LEN(str));

zend_string_hash_val(str);
php_printf("The string hash is %lu\n", ZSTR_H(str));

zend_string_forget_hash_val(str);
php_printf("The string hash is now cleared back to 0!");

zend_string_release(str);

字符串復(fù)制和內(nèi)存管理

zend_string API 的一個(gè)非常棒的特性是:允許某部分通過簡單的聲明“擁有”字符串。引擎不會(huì)在內(nèi)存復(fù)制字符串,而是遞增其引用計(jì)數(shù)(作為字符串zend_refcounted_h 的一部分)。這允許在代碼的多個(gè)地方共享一個(gè)內(nèi)存。

由此,當(dāng)我們討論“復(fù)制”一個(gè) zend_string 時(shí),實(shí)際上并沒有復(fù)制內(nèi)存中的任何東西。如果需要(這仍是可能的操作),之后我們來討論“復(fù)制”字符串。開始吧:

zend_string *foo, *bar, *bar2, *baz;

foo = zend_string_init("foo", strlen("foo"), 0); /* 創(chuàng)建變量foo,值為“foo” */
bar = zend_string_init("bar", strlen("bar"), 0); /* 創(chuàng)建變量bar,值為"bar" */

/* 創(chuàng)建變量bar2,共享變量bar的值。
  另外遞增"bar"字符串的引用計(jì)數(shù)到2 */
bar2 = zend_string_copy(bar);

php_printf("We just copied two strings\n");
php_printf("See : bar content : %s, bar2 content : %s\n", ZSTR_VAL(bar), ZSTR_VAL(bar2));

/* 在內(nèi)存中復(fù)制"bar"字符串,創(chuàng)建變量 baz,
使 baz 單獨(dú)擁有新創(chuàng)建的"bar"字符串 */
baz = zend_string_dup(bar, 0);

php_printf("We just duplicated 'bar' in 'baz'\n");
php_printf("Now we are free to change 'baz' without fearing to change 'bar'\n");

/* 更改第二個(gè)"bar"字符串的最后一個(gè)字符,
變?yōu)?baz" */
ZSTR_VAL(baz)[ZSTR_LEN(baz) - 1] = 'z';

/* 當(dāng)字符串改變時(shí),忘記舊哈希值(如果已計(jì)算),
因此其哈希值必須更改并重新計(jì)數(shù) */
zend_string_forget_hash_val(baz);

php_printf("'baz' content is now %s\n", ZSTR_VAL(baz));

zend_string_release(foo);  /* 銷毀(釋放)"foo"字符串 */
zend_string_release(bar);  /* 遞減"bar"字符串的引用計(jì)數(shù)到1 */
zend_string_release(bar2); /* 銷毀(釋放)bar和bar2變量中的"bar"字符串 */
zend_string_release(baz);  /* 銷毀(釋放)"baz"字符串 */

我們一開始僅分配 “foo” 和 “bar”。然后,我們創(chuàng)建 bar的副本到bar2字符串。這里,必須記?。涸趦?nèi)存中,barbar2 指向同一 C 字符串,更改一個(gè)將更改第二個(gè)。這是 zend_string_copy() 行為:它僅遞增 C 字符串的引用計(jì)數(shù)。

如果想要分離字符串,即想在內(nèi)存中擁有該字符串的兩個(gè)不同副本,我們必須使用 zend_string_dup()復(fù)制。然后我們將 bar2 變量字符串復(fù)制到 baz 變量。現(xiàn)在,baz 變量嵌入它的字符串副本,并且可以改變它而不影響 bar2 。這就是我們要做的:我們用‘z’改變了‘bar’最后的‘r’,之后,我們顯示它,并釋放所有字符串。

注意,我們忘記哈希值(如果它在之前已經(jīng)計(jì)算,則不需要考慮其細(xì)節(jié))。這是一個(gè)值得記住的好習(xí)慣。就像我們?cè)f過,如果 zend_string 作為 HashTables 的一部分,則使用哈希值。這在開發(fā)中是很常見的,并且改變字符串的值必須重新計(jì)算哈希值。忘記這一步驟將導(dǎo)致可能需要花一些時(shí)間去追蹤錯(cuò)誤。

字符串操作

zend_string API 允許其他操作,例如擴(kuò)展或縮小字符串,更改大小寫或比較字符串。目前尚未有連接字符串操作,但是很容易執(zhí)行:

zend_string *FOO, *bar, *foobar, *foo_lc;

FOO = zend_string_init("FOO", strlen("FOO"), 0);
bar = zend_string_init("bar", strlen("bar"), 0);

/* 將 zend_string 與 C 字符串文字進(jìn)行比較 */
if (!zend_string_equals_literal(FOO, "foobar")) {
    foobar = zend_string_copy(FOO);

    /* realloc() 將 C 字符串分配到更大的緩沖區(qū) */
    foobar = zend_string_extend(foobar, strlen("foobar"), 0);

    /* 在重新分配的足夠大的“FOO”之后,連接"bar" */
    memcpy(ZSTR_VAL(foobar) + ZSTR_LEN(FOO), ZSTR_VAL(bar), ZSTR_LEN(bar));
}

php_printf("This is my new string: %s\n", ZSTR_VAL(foobar));

/* 比較兩個(gè) zend_string */
if (!zend_string_equals(FOO, foobar)) {
    /*復(fù)制字符串并改為小寫*/
    foo_lc = zend_string_tolower(foo);
}

php_printf("This is FOO in lower-case: %s\n", ZSTR_VAL(foo_lc));

/* 釋放內(nèi)存 */
zend_string_release(FOO);
zend_string_release(bar);
zend_string_release(foobar);
zend_string_release(foo_lc);

使用 zval 訪問 zend_string

現(xiàn)在你知道如何管理和操作 zend_string,讓我們看看它們與 zval 容器的互動(dòng)。

注意

你必須熟悉 zval,如果不熟悉,閱讀Zvals專用章節(jié)。

宏將允許你將 zend_string 存儲(chǔ)到 zval,或從 zval 讀取 zend_string

zval myval;
zend_string *hello, *world;

zend_string_init(hello, "hello", strlen("hello"), 0);

/* 存儲(chǔ)字符串到 zval */
ZVAL_STR(&myval, hello);

/* 從 zval 的 zend_string 中讀取 C 字符串 */
php_printf("The string is %s", Z_STRVAL(myval));

zend_string_init(world, "world", strlen("world"), 0);

/* 將 zend_string 更改為 myval:將其替換為另一個(gè) */
Z_STR(myval) = world;

/* ... */

你必須記住的是,以ZSTR_***(s)開頭的每個(gè)宏都會(huì)作用到 zend_string

  • ZSTR_VAL()
  • ZSTR_LEN()
  • ZSTR_HASH()

每個(gè)以 Z_STR**(z) 開頭的宏都會(huì)作用于嵌入到 zval 中的 zend_string 。

  • Z_STRVAL()
  • Z_STRLEN()
  • Z_STRHASH()

還有一些你可能不需要的東西也存在。

PHP 的歷史和經(jīng)典的 C 字符串

簡單介紹一下。在 C 語言中,字符串是字符數(shù)組(char foo[])或者指向字符的指針(char *)。它們并不知道其長度,這就是它們?yōu)槭裁茨┪彩?NUL(知道字符串的開始和結(jié)尾,就可以知道它的長度)。

在 PHP 7 之前,zend_string 結(jié)構(gòu)還未出現(xiàn)。在那時(shí),還是使用傳統(tǒng)的 char * / int。你可能仍會(huì)在 PHP 源代碼中找到使用了罕見的 char * / int,而不是 zend_string。你也可能發(fā)現(xiàn) API 功能,可以一邊使用 zend_string,另一邊使用 char * / int來交互。

在任何可能的地方:使用 zend_string。那些罕見的沒有使用 zend_string 的地方,是因?yàn)樵谀抢锸褂盟鼈儾]有什么意義,但是你仍會(huì)發(fā)現(xiàn)在 PHP 源代碼中有很多對(duì) zend_string 的引用。

Interned zend_string

在這里簡單的介紹一下 interned 字符串。你在擴(kuò)展開發(fā)中應(yīng)該需要這樣的概念。Interned 字符串也和 OPCache 擴(kuò)展交互。

Interned 字符串是去重復(fù)的字符串。當(dāng)與 OPCache 一起使用時(shí),它還可以在請(qǐng)求之間循環(huán)使用。

假設(shè)你想要?jiǎng)?chuàng)建字符串“foo”。你更想做的是簡單地創(chuàng)建一個(gè)新字符串“foo”:

zend_string *foo;
foo = zend_string_init("foo", strlen("foo"), 0);

/* ... */

但是有一個(gè)問題:字符串是不是在你需要之前已經(jīng)創(chuàng)建了?當(dāng)你需要一個(gè)字符串時(shí),你的代碼會(huì)在PHP生命中的某個(gè)時(shí)刻執(zhí)行,這意味著在你需要完全相同的字符串(在我們的示例中為“ foo”)之前發(fā)生了一些代碼。

Interned 字符串是關(guān)于要求引擎去探查 interned 字符串存儲(chǔ),并且如果它能找到你的字符串,會(huì)重用已經(jīng)分配的指針。如果沒有找到:創(chuàng)建一個(gè)新的字符串并“intern” 它,這使得它可用于 PHP 源代碼的其他部分(其他擴(kuò)展,引擎本身等)。

這里有個(gè)例子:

zend_string *foo;
foo = zend_string_init("foo", strlen("foo"), 0);

foo = zend_new_interned_string(foo);

php_printf("This string is interned : %s", ZSTR_VAL(foo));

zend_string_release(foo);

上面的代碼創(chuàng)建了一個(gè)非常經(jīng)典的新 zend_string 。然后,我們將創(chuàng)建的 zend_string 傳遞給 zend_new_interned_string()。該函數(shù)在引擎 interned 字符串緩沖區(qū)查找相同的字符串(這里是“foo”)。如果找到它(意味著有人已經(jīng)創(chuàng)建了這樣的字符串),那么它將釋放你的字符串(可能釋放它),并且用 interned 字符串緩沖區(qū)中的字符串替代它。如果找不到:它將被添加到 interned 字符串緩沖區(qū),使它在將來可使用或可用于 PHP 的其他部分。

你必須注意內(nèi)存分配。Interned 字符串總是將 refcount 設(shè)為1,因?yàn)樗鼈儾槐乇灰?,由于它們?huì)和 interned 字符串緩沖區(qū)共享,因此不可被銷毀。

例:

zend_string *foo, *foo2;

foo  = zend_string_init("foo", strlen("foo"), 0);
foo2 = zend_string_copy(foo); /* 遞增 foo 的引用計(jì)數(shù) */

 /* 引用計(jì)數(shù)退回 1,即使現(xiàn)在字符串在三個(gè)不同的地方被使用 */
foo = zend_new_interned_string(foo);

/* 這沒有任何作用,因?yàn)?foo 是 interned */
zend_string_release(foo);

/*  這沒有任何作用,因?yàn)?foo2 是 interned*/
zend_string_release(foo2);

/* 在流程結(jié)束時(shí),PHP 將清除它的 interned 字符串緩沖區(qū),
  因此 free() 我們 "foo" 字符串本身 */

這都是關(guān)于垃圾收集的。

當(dāng)字符串是 interned,更改其 GC 標(biāo)志以添加  IS_STR_INTERNED 標(biāo)志,不管使用的是什么內(nèi)存分配類(基于永久或基于請(qǐng)求)。當(dāng)你想要復(fù)制或釋放字符串,都會(huì)檢查該標(biāo)志。如果是 interned 字符串,當(dāng)你復(fù)制該字符串時(shí),引擎不會(huì)遞增它的引用計(jì)數(shù)。但是如果你釋放字符串,它也不會(huì)遞減或釋放它。它不做任何事情。在進(jìn)程生命周期的最后,它會(huì)銷毀它的 interned 字符串緩沖區(qū),并且釋放你的 interned 字符串。

事實(shí)上,此過程比這更為復(fù)雜。如果你使用的是請(qǐng)求處理中的 interned 字符串,那么該字符串肯定被 interned。但是,如果你是在 PHP 處理一個(gè)請(qǐng)求時(shí)使用 interned 字符串,那么該字符串只會(huì)在當(dāng)前請(qǐng)求被 interned,并在之后會(huì)清理掉。如果你不使用 OPCache 擴(kuò)展,那這一切都是有效的,有時(shí)你不應(yīng)該使用它。

當(dāng)使用 OPCache 擴(kuò)展,如果你使用請(qǐng)求處理中的 interned 字符串,那么該字符串肯定被 interned ,并且和并行產(chǎn)生的每個(gè) PHP 的進(jìn)程或線程共享。另外,如果當(dāng)你處理一個(gè)請(qǐng)求時(shí)使用 interned 字符串,該字符串也將由 OPCache 本身進(jìn)行 interned,并且共享給并行產(chǎn)生的每個(gè) PHP 進(jìn)程或線程。

然后,在觸發(fā) OPCache 擴(kuò)展時(shí),會(huì)更改 Interned 字符串機(jī)制。OPCache 不僅允許從請(qǐng)求來的 interned 字符串,而且允許將它們共享給同一池的每個(gè) PHP 進(jìn)程。這樣做是使用了共享內(nèi)存。當(dāng)保存一個(gè) interned 字符串時(shí),OPCache 也會(huì)添加 IS_STR_PERMANENT 標(biāo)志到它的 GC 信息。該標(biāo)志表示用于結(jié)構(gòu)(這里是zend_string)的內(nèi)存分配是永久的,它可以是共享的只讀內(nèi)存段。

Interned 字符串可節(jié)省內(nèi)存,因?yàn)樵趦?nèi)存中,同樣的字符串不會(huì)再被保存。但是當(dāng)它經(jīng)常需要查找 interned 字符串存儲(chǔ)時(shí),可能會(huì)浪費(fèi)一些 CPU 時(shí)間,即使該進(jìn)程如今已經(jīng)優(yōu)化了。作為一名擴(kuò)展設(shè)計(jì)師,這是全局規(guī)則:

  • 如果使用了 OPCache(應(yīng)該會(huì)),并且需要?jiǎng)?chuàng)建只讀字符串:請(qǐng)使用 interned 字符串。
  • 如果你需要的字符串是你確切知道 PHP 會(huì)有的 interned(眾所周知的 PHP 字符串,例如“php” 或 “str_replace”), 請(qǐng)使用 interned 字符串。
  • 如果字符串不是只讀,且在創(chuàng)建之后可以/應(yīng)該修改,請(qǐng)不要使用 interned 字符串。
  • 如果字符串在未來不太可能被重用,請(qǐng)不要使用 interned 字符串。

警告

不要試圖修改(寫入)一個(gè) interned 字符串,否則很可能崩潰。

Interned 字符串詳情請(qǐng)看 Zend/zend_string.c。

關(guān)于php的字符串如何管理 zend_string就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。


網(wǎng)頁名稱:php的字符串如何管理zend_string
網(wǎng)站地址:http://weahome.cn/article/jjhpes.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部