php字符串采用zend_string结构体管理,包含引用计数gc、预计算哈希值h、字节长度len和柔性数组val[1],实现二进制安全、o(1)长度获取与内存共享。

PHP字符串不是C语言那种以\0结尾的裸指针,而是带元信息的结构体——从PHP 7开始,统一用zend_string管理,长度、引用计数、哈希值全存好了,不用每次调用strlen()去遍历。
zend_string结构里到底存了什么
它不是一个简单的char*,而是一个紧凑的内存块:
-
gc字段:包含引用计数(refcount)和类型标记,决定何时释放内存 -
h字段:字符串内容的哈希值,创建时就算好,后续做数组键或比较时直接复用 -
len字段:明确存着字节长度,strlen()变成O(1)操作 -
val[1]:柔性数组,紧贴结构体后面存放实际字节数据,没有额外padding
这意味着"hello"和"你好"在内存里都按真实字节数存储(前者5字节,后者UTF-8下6字节),但长度信息不依赖<p>这意味着<code>"hello"和"你好"在内存里都按真实字节数存储(前者5字节,后者UTF-8下6字节),但长度信息不依赖\0终止符——所以PHP字符串是二进制安全的,能含\0、\xFF等任意字节。
<p>这意味着<code>"hello"和"你好"在内存里都按真实字节数存储(前者5字节,后者UTF-8下6字节),但长度信息不依赖\0终止符——所以PHP字符串是二进制安全的,能含\0、\xFF等任意字节。、\xFF等任意字节。为什么substr("你好", 0, 2)会乱码
因为substr()只认字节,不管字符边界。UTF-8下"你好"是0xE4 0xBD 0xA0 0xE4 0xBD 0x91共6字节,substr($s, 0, 2)取前2字节0xE4 0xBD,这不是合法UTF-8序列,解码就成。
- 单字节函数(
strlen、substr、strpos)一律按字节操作,不解析编码 - 多字节函数(
mb_strlen、mb_substr)必须传编码参数,如"UTF-8",内部会按UTF-8规则跳过非首字节 - 没传编码或传错(比如用
"GBK"解UTF-8字符串),mb_*也会出错
引用计数和字符串驻留怎么影响实际代码
两个变量赋值同一字符串字面量时,底层可能共享同一个zend_string结构,只增引用计数;但一旦修改(如$a[0] = 'X'),就会触发“写时复制”(copy-on-write),生成新副本。
- 字符串字面量(如
$a = "test")默认驻留,相同内容只存一份 - 拼接、截取、替换等操作通常产生新
zend_string,旧的引用计数减一,归零即释放 - 用
str_replace()处理大字符串时,频繁分配/释放会触发GC,比预期内存占用高
真正容易被忽略的是:你看到的$str变量本身只是指向zend_string的句柄,它的“值”不可见,所有操作都经由引擎间接完成——所以直接unset($str)并不立即释放内存,只减少引用计数。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











