百分百源码网-让建站变得如此简单! 登录 注册 签到领金币!

主页 | 如何升级VIP | TAG标签

当前位置: 主页>网站教程>网页制作> php的字符串治理 zend_string
分享文章到:

php的字符串治理 zend_string

发布时间:09/01 来源:未知 浏览: 关键词:

构造和拜访宏

这里是简便的zend_string构造:

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

如你所见,该构造嵌入了一个 zend_refcounted_h 标头。这个是内存治理和援用需要用到的。 由于该字符串很有大概作为哈希表检查的关键字,因此它在 h字段中嵌入了其哈希值。这是无符号长整型 zend_ulong。仅在需要对 zend_string 停止哈希处置时会用到,特殊是和哈希表:zend_array一起用时。这很有大概。

如你所知,字符串知道其长度为 len 字段,以支撑“二进制字符串。二进制字符串是嵌入一个或多个 NUL 字符(\0)的字符串。当传递给库函数,那些字符串会被截断,不然没法准确运算其长度。所以在 zend_string 中,字符串的长度总是已知的。请留意,该长度运算的 ASCII 字符(字节),不运算最后的NUL,而是运算终究的中心的 NUL。例如,字符串 “foo” 在 zend_string 中储备为 “foo\0”,且它的长度为3。别的,字符串 “foo\0bar” 将储备为 “foo\0bar\0”,且其长度为7。

终究,该字符储备在 char[1]。这不是 char *,而是 char[1]。为什么?这是一种称为 “C struct hack” 的内存优化(你可以使用带有这些术语的搜索引擎)。根本上,它同意引擎为 zend_string 构造和要储备的字符分配空间,作为一个独自的 C 指针。这优化了内存,由于内存拜访将是一个持续分配的块,而不是两个分离的块(一个用于储备 zend_string *,另一个用于储备 char *)。

必需记住这种 struct hack,由于内存规划看起来像 C 字符位于 C zend_string 构造的末尾,因此当使用 C 调试器(或调试字符串)时大概会感受到/看到过。该 hack 是完全由 API 治理,当你操纵 zend_string构造时会用到。

../../../_images/zend_string_memory_layout.png

使用 zend_string API

简便用例

像 Zvals,你不需要手动操纵 zend_string 内部字段,而总是为此使用宏。还存在触发字符串操纵的宏。这并不是函数,而是宏,都储备在必需的 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);

上面简便的例子为你展现了根本的字符串治理。应当为 zend_string_init() 函数(实际上是宏,但先让我们忽略它)给出完全的 char * C 字符串和它的长度。类型为 int 的最后一个参数应当为 0 或 1。假如传递0,则要求引擎通过 Zend 内存治理使用恳求绑定的堆分配。这种分配在当前恳求完毕后时烧毁。假如你不这么做,则在调试版本中,引擎会提示你内存走漏。假如传递1,则要求了所谓的“耐久”分配,引擎将使用传统的 C malloc() 调取,并且不会以任何方式追踪内存分配。

留意

假如你需要更多有关内存治理的信息,你可以阅读专用章节。

然后,我们来显示字符串。我们使用 ZSTR_VAL() 宏拜访字符数组。ZSTR_LEN() 同意拜访长度信息。zend_string 相关宏都以 ZSTR_**() 开端,留意和 Z_STR**() 宏不一样。

留意

长度使用 size_t 类型储备,为了显示它,printf() 必需使用 “%zd”。你应当总是使用准确的printf()格局。不然大概会致使利用程序崩溃或创立平安问题不然大概会致使内存走漏和。有关 printf() 格局的具体信息,请拜访此链接

最后,我们使用 zend_string_release()开释字符串。该开释是强迫的。这与内存治理有关。“开释”是一个简便的操纵:字符串的援用计数递减,假如减到0,API会为你开释字符串。假如健忘开释字符串,则很大概造成内存走漏。

留意

在 C 说话中,你必需总是思考内存治理。假如你分配——不管是直接使用 malloc(),或者使用能为你这样做的 API,在某些时候你必需使用 free()。不然大概会致使内存走漏,并转换为任何人都不克不及平安使用的糟糕设计程序。

玩转 hash

假如你需要拜访哈希值,可使用 ZSTR_H()。但创立 zend_string 时,不会主动运算其哈希值。而当将该字符串与 HashTable API 一起使用时,它将为你完成。假如你强迫马上运算哈希值,可使用 ZSTR_HASH()zend_string_hash_val()。当哈希值被运算出来,它会被留存起来并且不再被运算。不管怎样,你必需使用 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);

字符串复制和内存治理

zend_string API 的一个非常棒的特性是:同意某部分通过简便的声明“具有”字符串。引擎不会在内存复制字符串,而是递增其援用计数(作为字符串zend_refcounted_h 的一部分)。这同意在代码的多个地方同享一个内存。

由此,当我们计议“复制”一个 zend_string 时,实际上并没有复制内存中的任何东西。假如需要(这仍是大概的操纵),之后我们来计议“复制”字符串。开端吧:

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

foo = zend_string_init("foo", strlen("foo"), 0); /* 创立变量foo,值为“foo” */
bar = zend_string_init("bar", strlen("bar"), 0); /* 创立变量bar,值为"bar" */

/* 创立变量bar2,同享变量bar的值。
  别的递增"bar"字符串的援用计数到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));

/* 在内存中复制"bar"字符串,创立变量 baz,
使 baz 独自具有新创立的"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"字符串的最后一个字符,
变为"baz" */
ZSTR_VAL(baz)[ZSTR_LEN(baz) - 1] = 'z';

/* 当字符串改动时,健忘旧哈希值(假如已运算),
因此其哈希值必需更换并从新计数 */
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"字符串的援用计数到1 */
zend_string_release(bar2); /* 烧毁(开释)bar和bar2变量中的"bar"字符串 */
zend_string_release(baz);  /* 烧毁(开释)"baz"字符串 */

我们一开端仅分配 “foo” 和 “bar”。然后,我们创立 bar的副本到bar2字符串。这里,必需记住:在内存中,barbar2 指向统一 C 字符串,更换一个将更换第二个。这是 zend_string_copy() 行动:它仅递增 C 字符串的援用计数。

假如想要别离字符串,即想在内存中具有该字符串的两个不一样副本,我们必需使用 zend_string_dup()复制。然后我们将 bar2 变量字符串复制到 baz 变量。此刻,baz 变量嵌入它的字符串副本,并且可以改动它而不影响 bar2 。这就是我们要做的:我们用‘z’改动了‘bar’最后的‘r’,之后,我们显示它,并开释所有字符串。

留意,我们健忘哈希值(假如它在此前已经运算,则不需要思考其细节)。这是一个值得记住的好习惯。就像我们曾说过,假如 zend_string 作为 HashTables 的一部分,则使用哈希值。这在开发中是很常见的,并且改动字符串的值必需从新运算哈希值。健忘这一步骤将致使大概需要花一些时间去追踪错误。

字符串操纵

zend_string API 同意其他操纵,例如扩展或缩小字符串,更换大小写或比力字符串。当前尚未有连接字符串操纵,但是很容易施行:

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 字符串文字停止比力 */
if (!zend_string_equals_literal(FOO, "foobar")) {
    foobar = zend_string_copy(FOO);

    /* realloc() 将 C 字符串分配到更大的缓冲区 */
    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)) {
    /*复制字符串并改为小写*/
    foo_lc = zend_string_tolower(foo);
}

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

/* 开释内存 */
zend_string_release(FOO);
zend_string_release(bar);
zend_string_release(foobar);
zend_string_release(foo_lc);

使用 zval 拜访 zend_string

此刻你知道怎样治理和操纵 zend_string,让我们看看它们与 zval 容器的互动。

留意

你必需熟知 zval,假如不熟知,阅读Zvals专用章节。

宏将同意你将 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 的历史和经典的 C 字符串

简便介绍一下。在 C 说话中,字符串是字符数组(char foo[])或者指向字符的指针(char *)。它们并不知道其长度,这就是它们为什么末尾是 NUL(知道字符串的开端和结尾,就可以知道它的长度)。

在 PHP 7 此前,zend_string 构造还未显现。在那时,还是使用传统的 char * / int。你大概仍会在 PHP 源代码中寻到使用了很少见到的 char * / int,而不是 zend_string。你也大概发明 API 功效,可以一边使用 zend_string,另一边使用 char * / int来交互。

在任何大概的地方:使用 zend_string。那些很少见到的没有使用 zend_string 的地方,是由于在那里使用它们并没有什么意义,但是你仍会发明在 PHP 源代码中有许多对 zend_string 的援用。

Interned zend_string

在这里简便的介绍一下 interned 字符串。你在扩展开发中应当需要这样的概念。Interned 字符串也和 OPCache 扩展交互。

Interned 字符串是去反复的字符串。当与 OPCache 一起使用时,它还可以在恳求之间轮回使用。

假设你想要创立字符串“foo”。你更想做的是简便地创立一个新字符串“foo”:

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

/* ... */

但是有一个问题:字符串是不是在你需要此前已经创立了?当你需要一个字符串时,你的代码会在PHP生命中的某个时刻施行,这意味着在你需要完全雷同的字符串(在我们的示例中为“ foo”)此前发生了一些代码。

Interned 字符串是关于要求引擎去探查 interned 字符串储备,并且假如它能寻到你的字符串,会重用已经分配的指针。假如没有寻到:创立一个新的字符串并“intern” 它,这使得它可用于 PHP 源代码的其他部分(其他扩展,引擎本身等)。

这里有个例子:

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);

上面的代码创立了一个非常经典的新 zend_string 。然后,我们将创立的 zend_string 传递给 zend_new_interned_string()。该函数在引擎 interned 字符串缓冲区查寻雷同的字符串(这里是“foo”)。假如寻到它(意味着有人已经创立了这样的字符串),那么它将开释你的字符串(大概开释它),并且用 interned 字符串缓冲区中的字符串替换它。假如寻不到:它将被增加到 interned 字符串缓冲区,使它在未来可使用或可用于 PHP 的其他部分。

你必需留意内存分配。Interned 字符串总是将 refcount 设为1,由于它们不必被援用,由于它们会和 interned 字符串缓冲区同享,因此不成被烧毁。

例:

zend_string *foo, *foo2;

foo  = zend_string_init("foo", strlen("foo"), 0);
foo2 = zend_string_copy(foo); /* 递增 foo 的援用计数 */

 /* 援用计数退回 1,即便此刻字符串在三个不一样的地方被使用 */
foo = zend_new_interned_string(foo);

/* 这没有任何作用,由于 foo 是 interned */
zend_string_release(foo);

/*  这没有任何作用,由于 foo2 是 interned*/
zend_string_release(foo2);

/* 在流程完毕时,PHP 将清除它的 interned 字符串缓冲区,
  因此 free() 我们 "foo" 字符串本身 */

这都是关于垃圾收集的。

当字符串是 interned,更换其 GC 标记以增加 IS_STR_INTERNED 标记,不管使用的是啥内存分配类(基于永远或基于恳求)。当你想要复制或开释字符串,都会检查该标记。假如是 interned 字符串,当你复制该字符串时,引擎不会递增它的援用计数。但是假如你开释字符串,它也不会递减或开释它。它不做任何事情。在进程生命周期的最后,它会烧毁它的 interned 字符串缓冲区,并且开释你的 interned 字符串。

事实上,此历程比这更为复杂。假如你使用的是恳求处置中的 interned 字符串,那么该字符串必定被 interned。但是,假如你是在 PHP 处置一个恳求时使用 interned 字符串,那么该字符串只会在当前恳求被 interned,并在之后会清算掉。假如你不使用 OPCache 扩展,那这一切都是有效的,有时你不该该使用它。

当使用 OPCache 扩展,假如你使用恳求处置中的 interned 字符串,那么该字符串必定被 interned ,并且和并行发生的每个 PHP 的进程或线程同享。别的,假如当你处置一个恳求时使用 interned 字符串,该字符串也将由 OPCache 本身停止 interned,并且同享给并行发生的每个 PHP 进程或线程。

然后,在触发 OPCache 扩展时,会更换 Interned 字符串机制。OPCache 不仅同意从恳求来的 interned 字符串,并且同意将它们同享给统一池的每个 PHP 进程。这样做是使用了同享内存。当留存一个 interned 字符串时,OPCache 也会增加 IS_STR_PERMANENT 标记到它的 GC 信息。该标记表示用于构造(这里是zend_string)的内存分配是永远的,它可以是同享的只读内存段。

Interned 字符串可节约内存,由于在内存中,一样的字符串不会再被留存。但是当它经常需要查寻 interned 字符串储备时,大概会白费一些 CPU 时间,即便该进程如今已经优化了。作为一位扩展设计师,这是全局规则:

  • 假如使用了 OPCache(应当会),并且需要创立只读字符串:请使用 interned 字符串。
  • 假如你需要的字符串是你确切知道 PHP 会有的 interned(一目了然的 PHP 字符串,例如“php” 或 “str_replace”), 请使用 interned 字符串。
  • 假如字符串不是只读,且在创立之后可以/应当修改,请不要使用 interned 字符串。
  • 假如字符串在将来不太大概被重用,请不要使用 interned 字符串。

警告

不要试图修改(写入)一个 interned 字符串,不然很大概崩溃。

Interned 字符串详情请看 Zend/zend_string.c。

以上就是php的字符串治理 zend_string的具体内容,更多请关注百分百源码网其它相关文章!

打赏

打赏

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

百分百源码网 建议打赏1~10元,土豪随意,感谢您的阅读!

共有151人阅读,期待你的评论!发表评论
昵称: 网址: 验证码: 点击我更换图片
最新评论

本文标签

广告赞助

能出一分力是一分吧!

订阅获得更多模板

本文标签

广告赞助

订阅获得更多模板