php的字符串管理zend_string

分宜網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),分宜網(wǎng)站設(shè)計制作,有大型網(wǎng)站制作公司豐富經(jīng)驗。已為分宜上千提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\外貿(mào)網(wǎng)站建設(shè)要多少錢,請找那個售后服務(wù)好的分宜做網(wǎng)站的公司定做!字符串管理:zend_string

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

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

結(jié)構(gòu)和訪問宏

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

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

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

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

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

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

使用 zend_string API簡單用例

像 Zvals,你不需要手動操作 zend_string內(nèi)部字段,而總是為此使用宏。還存在觸發(fā)字符串操作的宏。這并不是函數(shù),而是宏,都存儲在必需的 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ù)(實際上是宏,但先讓我們忽略它)給出完整的 char *C 字符串和它的長度。類型為 int 的最后一個參數(shù)應(yīng)該為 0 或 1。如果傳遞0,則要求引擎通過 Zend 內(nèi)存管理使用請求綁定的堆分配。這種分配在當(dāng)前請求結(jié)束后時銷毀。如果你不這么做,則在調(diào)試版本中,引擎會提醒你內(nèi)存泄漏。如果傳遞1,則要求了所謂的“持久”分配,引擎將使用傳統(tǒng)的 C malloc()調(diào)用,并且不會以任何方式追蹤內(nèi)存分配。

注意

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

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

注意

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

最后,我們使用 zend_string_release()釋放字符串。該釋放是強(qiáng)制的。這與內(nèi)存管理有關(guān)。“釋放”是一個簡單的操作:字符串的引用計數(shù)遞減,如果減到0,API會為你釋放字符串。如果忘記釋放字符串,則很可能造成內(nèi)存泄漏。

注意

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

玩轉(zhuǎn) hash

如果你需要訪問哈希值,可使用 ZSTR_H()。但創(chuàng)建 zend_string時,不會自動計算其哈希值。而當(dāng)將該字符串與 HashTable API 一起使用時,它將為你完成。如果你強(qiáng)制立即計算哈希值,可使用 ZSTR_HASH()zend_string_hash_val()。當(dāng)哈希值被計算出來,它會被保存起來并且不再被計算。無論如何,你必須使用 zend_string_forget_hash_val()重新計算——因為你改變了字符串的值:

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

由此,當(dāng)我們討論“復(fù)制”一個 zend_string時,實際上并沒有復(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"字符串的引用計數(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 單獨擁有新創(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");

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

/* 當(dāng)字符串改變時,忘記舊哈希值(如果已計算),
因此其哈希值必須更改并重新計數(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"字符串的引用計數(shù)到1 */
zend_string_release(bar2); /* 銷毀(釋放)bar和bar2變量中的"bar"字符串 */
zend_string_release(baz);  /* 銷毀(釋放)"baz"字符串 */

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

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

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

字符串操作

zend_stringAPI 允許其他操作,例如擴(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));

/* 比較兩個 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容器的互動。

注意

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

宏將允許你將 zend_string存儲到 zval,或從 zval讀取 zend_string

zval myval;
zend_string *hello, *world;

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

/* 存儲字符串到 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:將其替換為另一個 */
Z_STR(myval) = world;

/* ... */

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

ZSTR_VAL()ZSTR_LEN()ZSTR_HASH()

每個以 Z_STR**(z)開頭的宏都會作用于嵌入到 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)。在那時,還是使用傳統(tǒng)的 char * / int。你可能仍會在 PHP 源代碼中找到使用了罕見的 char * / int,而不是 zend_string。你也可能發(fā)現(xiàn) API 功能,可以一邊使用 zend_string,另一邊使用 char * / int來交互。

在任何可能的地方:使用 zend_string。那些罕見的沒有使用 zend_string的地方,是因為在那里使用它們并沒有什么意義,但是你仍會發(fā)現(xiàn)在 PHP 源代碼中有很多對 zend_string的引用。

Interned zend_string

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

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

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

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

/* ... */

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

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

這里有個例子:

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)建了一個非常經(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,因為它們不必被引用,由于它們會和 interned 字符串緩沖區(qū)共享,因此不可被銷毀。

例:

zend_string *foo, *foo2;

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

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

/* 這沒有任何作用,因為 foo 是 interned */
zend_string_release(foo);

/*  這沒有任何作用,因為 foo2 是 interned*/
zend_string_release(foo2);

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

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

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

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

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

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

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

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

警告

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

Interned 字符串詳情請看 Zend/zend_string.c。

文章名稱:php的字符串管理zend_string
網(wǎng)站網(wǎng)址:http://m.kartarina.com/article34/cpcese.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供企業(yè)建站服務(wù)器托管定制網(wǎng)站定制開發(fā)網(wǎng)站內(nèi)鏈軟件開發(fā)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管
主站蜘蛛池模板: 亚洲a无码综合a国产av中文| 精品无码久久久久国产动漫3d| 特级小箩利无码毛片| 国产成人无码AV在线播放无广告| 国产av激情无码久久| 无码人妻丝袜在线视频| 成在人线av无码免费高潮水| 日韩人妻无码一区二区三区久久| 精品无码中文视频在线观看| 日韩乱码人妻无码中文字幕| 久久久久久av无码免费看大片| 亚洲国产精品无码久久| 中文字幕人妻无码一夲道| 无码人妻aⅴ一区二区三区| 无码囯产精品一区二区免费| 国产精品无码DVD在线观看| 在线看片无码永久免费视频 | 99久久人妻无码精品系列| 无码视频在线播放一二三区| 无码精品国产dvd在线观看9久| 亚洲精品无码专区久久久 | 亚洲av无码一区二区三区乱子伦| 午夜无码人妻av大片色欲| 蜜桃无码AV一区二区| 国产成人无码一区二区在线播放 | yy111111少妇影院无码| 亚洲AV无码一区二区乱子仑| 无码毛片视频一区二区本码| 国产成人无码18禁午夜福利p| 亚洲无码日韩精品第一页| 永久免费AV无码网站在线观看| 97免费人妻无码视频| 久久久久亚洲av无码专区喷水 | 小泽玛丽无码视频一区| 久久久无码精品人妻一区| 久久精品日韩av无码| 无码少妇一区二区浪潮免费| 人妻少妇精品无码专区动漫| 特级小箩利无码毛片| 毛片亚洲AV无码精品国产午夜| 日韩人妻系列无码专区|