热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

php的字符串管理zend_string

字符串管理zend_string任何程序都需要管理字符串,在这里,我们将详细介绍适合PHP需求的自定义解决方案:zend_string,每次PHP需要使用字符串时,都会使用zend_string结构。

字符串管理:zend_string

任何程序都需要管理字符串。在这里,我们将详细介绍适合 PHP 需求的自定义解决方案:zend_string。每次 PHP 需要使用字符串时,都会使用 zend_string 结构。该结构仅仅是 C 语言的 char * 字符串类型的简单精简包装。

它添加了内存管理的功能,所以同一字符串可以在多个地方共享,而无需重复。另外,一些字符串是“内部的”,即“持久的”分配,并通过内存管理特殊管理,以便它们不会在多个请求中被销毁。之后,那些从Zend 内存管理获得永久分配。

相关学习推荐:PHP编程从入门到精通

结构和访问宏

这里是简单的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结构时会用到。

使用 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的详细内容,更多请关注 第一PHP社区 其它相关文章!


推荐阅读
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • MyBatis错题分析解析及注意事项
    本文对MyBatis的错题进行了分析和解析,同时介绍了使用MyBatis时需要注意的一些事项,如resultMap的使用、SqlSession和SqlSessionFactory的获取方式、动态SQL中的else元素和when元素的使用、resource属性和url属性的配置方式、typeAliases的使用方法等。同时还指出了在属性名与查询字段名不一致时需要使用resultMap进行结果映射,而不能使用resultType。 ... [详细]
  • 如何修改路由器密码?路由器登录密码和无线密码的修改方法
    本文介绍了修改路由器密码的两种方法:一是修改路由器登录口令,需要进入路由器后台进行操作;二是修改无线连接密码,通过进入路由器后台的无线设置和无线安全设置进行修改。详细步骤包括复位处理、登录路由器后台、选择系统工具、填入用户名和用户密码、保存修改等。 ... [详细]
  • 本文介绍了2019年上半年内蒙古计算机软考考试的报名通知和考试时间。考试报名时间为3月1日至3月23日,考试时间为2019年5月25日。考试分为高级、中级和初级三个级别,涵盖了多个专业资格。报名采取网上报名和网上缴费的方式进行,报考人员可登录内蒙古人事考试信息网进行报名。详细内容请点击查看。 ... [详细]
  • 本文介绍了作者在开发过程中遇到的问题,即播放框架内容安全策略设置不起作用的错误。作者通过使用编译时依赖注入的方式解决了这个问题,并分享了解决方案。文章详细描述了问题的出现情况、错误输出内容以及解决方案的具体步骤。如果你也遇到了类似的问题,本文可能对你有一定的参考价值。 ... [详细]
  • 本文介绍了Java高并发程序设计中线程安全的概念与synchronized关键字的使用。通过一个计数器的例子,演示了多线程同时对变量进行累加操作时可能出现的问题。最终值会小于预期的原因是因为两个线程同时对变量进行写入时,其中一个线程的结果会覆盖另一个线程的结果。为了解决这个问题,可以使用synchronized关键字来保证线程安全。 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了如何使用python从列表中删除所有的零,并将结果以列表形式输出,同时提供了示例格式。 ... [详细]
author-avatar
宁波南诚装饰_886
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有