• 售前

  • 售后

热门帖子
入门百科

PHP赋值的内部是怎样跑的详解

[复制链接]
我心如烟卸 显示全部楼层 发表于 2021-10-26 13:53:33 |阅读模式 打印 上一主题 下一主题
前言
在PHP中,一个变量被赋值,内部到底履历了怎样的逻辑判断呢?
PHP在内核中是通过zval这个布局体来存储变量的,它的定义在Zend/zend.h文件里
  1. struct _zval_struct {zvalue_value value; /* 变量的值 */zend_uint refcount__gc;zend_uchar type; /* 变量当前的数据类型 */zend_uchar is_ref__gc;};typedef struct _zval_struct zval;//在Zend/zend_types.h里定义的:typedef unsigned int zend_uint;typedef unsigned char zend_uchar;
复制代码
利用xdebug的xdebug_debug_zval函数可以打印出变量的refcount,is_ref的值。
  1. $a = 'Hello World';$b = $a;
复制代码
以上内容在内核中怎么实行呢?
  1. zval *helloval;MAKE_STD_ZVAL(helloval);ZVAL_STRING(helloval, "Hello World", 1);zend_hash_add(EG(active_symbol_table), "a", sizeof("a"),&helloval, sizeof(zval*), NULL);ZVAL_ADDREF(helloval); //这句很特殊,我们显式的增加了helloval结构体的refcountzend_hash_add(EG(active_symbol_table), "b", sizeof("b"),&helloval, sizeof(zval*), NULL);
复制代码
可以看出来,当变量赋值的时候,其实两个变量指向的是同一个地址空间。那么题目来了,如果指向同一个地址空间,那不是修改a,b也会跟着改变。这就涉及php的 写时复制机制 。 以上代码,如果后面一行为 $b = '123' 判断过程如下:
      
  • 如果这个变量的zval部门的refcount小于2,代表没有别的变量在用,则直接修改这个值  
  • 否则,复制一份zval 的值,减少原zval的refcount的值,初始化新的zval的refcount,修改新复制的zval
简单变量

先引用赋值后普通赋值
  1. var_dump(memory_get_usage());$a = '1234567890';xdebug_debug_zval('a');var_dump(memory_get_usage());$b = &$a;xdebug_debug_zval('a','b');var_dump(memory_get_usage());$c = $a;xdebug_debug_zval('a','b','c');var_dump(memory_get_usage());$a = '1234567890';var_dump(memory_get_usage());$b = &$a;var_dump(memory_get_usage());$c = $a;
复制代码
输出内容如下:
  1. int(121672)
  2. a: (refcount=1, is_ref=0)='1234567890'
  3. int(121776)
  4. a: (refcount=2, is_ref=1)='1234567890'
  5. b: (refcount=2, is_ref=1)='1234567890'
  6. int(121824)
  7. a: (refcount=2, is_ref=1)='1234567890'
  8. b: (refcount=2, is_ref=1)='1234567890'
  9. c: (refcount=1, is_ref=0)='1234567890'
  10. int(121928)
复制代码
$a 赋值,开发了104byte空间,变量a refcount=1,is_ref=0
$b 赋值,开发了48byte空间,变量a refcount=2,is_ref=1。48byte是符号表占用,a,b实行同一个地址空间
$c 赋值,开发了104byte空间。由于a,b是引用,所以在c赋值的时候,会开发新空间,复制a zval内容,并初始化refcount,is_ref,所以a 的refcount不变,c 的refcount=1
先普通赋值后引用赋值
  1. var_dump(memory_get_usage());$a = '1234567890';xdebug_debug_zval('a');var_dump(memory_get_usage());$b = $a;xdebug_debug_zval('a','b');var_dump(memory_get_usage());$c = &$a;xdebug_debug_zval('a','b','c');var_dump(memory_get_usage());
复制代码
输出内容如下:
  1. int(121672)
  2. a: (refcount=1, is_ref=0)='1234567890'
  3. int(121776)
  4. a: (refcount=2, is_ref=0)='1234567890'
  5. b: (refcount=2, is_ref=0)='1234567890'
  6. int(121824)
  7. a: (refcount=2, is_ref=1)='1234567890'
  8. b: (refcount=1, is_ref=0)='1234567890'
  9. c: (refcount=2, is_ref=1)='1234567890'
  10. int(121928)
复制代码
$a 赋值,开发了104byte空间,变量a refcount=1,is_ref=0
$b 赋值,开发了48byte空间,变量a refcount=2,is_ref=1。48byte是符号表占用,a,b指向同一个地址空间
$c 赋值,开发了104byte空间。由于a,c是引用,须要与b隔离开来,因此会赋值原有的zval,初始化zval,将a,c指向新复制的zval,同时原有的zval refcount-1
数组
  1. $arr = [0=>'one'];
  2. xdebug_debug_zval('arr');
  3. $arr[1] = $arr;
  4. xdebug_debug_zval('arr');
  5. $arr[2] = $arr;
  6. xdebug_debug_zval('arr');
  7. unset($arr[1]);
  8. xdebug_debug_zval('arr');
  9. unset($arr[2]);
  10. xdebug_debug_zval('arr');
复制代码
输出内容如下:
  1. arr: (refcount=1, is_ref=0)=array ( 0 => (refcount=1, is_ref=0)='one')
  2. )
  3. arr: (refcount=1, is_ref=0)=array (
  4. 0 => (refcount=2, is_ref=0)='one',
  5. 1 => (refcount=1, is_ref=0)=array (
  6. 0 => (refcount=2, is_ref=0)='one'
  7. )
  8. )
  9. arr: (refcount=1, is_ref=0)=array (
  10. 0 => (refcount=3, is_ref=0)='one',
  11. 1 => (refcount=2, is_ref=0)=array (
  12. 0 => (refcount=3, is_ref=0)='one'),
  13. 2 => (refcount=1, is_ref=0)=array (
  14. 0 => (refcount=3, is_ref=0)='one',
  15. 1 => (refcount=2, is_ref=0)=array (...)
  16. )
  17. )
  18. arr: (refcount=1, is_ref=0)=array (
  19. 0 => (refcount=3, is_ref=0)='one',
  20. 2 => (refcount=1, is_ref=0)=array (
  21. 0 => (refcount=3, is_ref=0)='one',
  22. 1 => (refcount=1, is_ref=0)=array (...)
  23. )
  24. )
  25. arr: (refcount=1, is_ref=0)=array (
  26. 0 => (refcount=1, is_ref=0)='one'
  27. )
  28. $arr = [0=>'one'];xdebug_debug_zval('arr');$arr[1] = &$arr;xdebug_debug_zval('arr');$arr[2] = $arr;xdebug_debug_zval('arr');unset($arr[1]);xdebug_debug_zval('arr');unset($arr[2]);xdebug_debug_zval('arr');
复制代码
输出内容如下:
  1. arr: (refcount=1, is_ref=0)=array (
  2. 0 => (refcount=1, is_ref=0)='one'
  3. )
  4. arr: (refcount=2, is_ref=1)=array (
  5. 0 => (refcount=1, is_ref=0)='one',
  6. 1 => (refcount=2, is_ref=1)=...
  7. )
  8. arr: (refcount=3, is_ref=1)=array (
  9. 0 => (refcount=2, is_ref=0)='one',
  10. 1 => (refcount=3, is_ref=1)=...,
  11. 2 => (refcount=2, is_ref=0)=array (
  12. 0 => (refcount=2, is_ref=0)='one',
  13. 1 => (refcount=3, is_ref=1)=...,
  14. 2 => (refcount=2, is_ref=0)=...)
  15. )
  16. arr: (refcount=2, is_ref=1)=array (
  17. 0 => (refcount=2, is_ref=0)='one',
  18. 2 => (refcount=2, is_ref=0)=array (
  19. 0 => (refcount=2, is_ref=0)='one',
  20. 1 => (refcount=2, is_ref=1)=...,
  21. 2 => (refcount=2, is_ref=0)=...)
  22. )
  23. arr: (refcount=2, is_ref=1)=array (
  24. 0 => (refcount=2, is_ref=0)='one'
  25. )
复制代码
上面段测试代码很相似,差别只在arr[1]是否是引用赋值。
arr[1]非引用赋值的环境,arr[0]的refcount = 赋值次数+1,实行两次unset之后,arr,arr[0]的refcount都跟开始定义的时候一致。 arr[1]引用赋值的环境,arr[0]的refcount = 非引用赋值次数+1,实行两次unset之后,arr,arr[0] 的refcount都无法回到定义的时候的值。
紧张缘故原由在于arr[1]引用赋值,构成一个递归操纵。 但是如果,至于这个refcount,真的说不明白。当没有arr[2]赋值的时候,实行unset, arr refcount能回到1 。从下面这张图更加清晰看出内部递归引用


当出现上面这种环境,refcount本该=1,但实际上面没有被设置为1,这种环境就会出现内存走漏。上面代码循环实行100次,内存从一开始121096 上升到169224,内存占用上升了5k 。
对象
  1. $user = new User();
  2. $m = $user;
  3. $user->user ='';
  4. $user->name = 'sdfsdfs';
  5. xdebug_debug_zval('user','m');
复制代码
以上内容输出
  1. (refcount=2, is_ref=0)=class User {
  2. public $name = (refcount=1, is_ref=0)='sdfsdfs';
  3. public $model = (refcount=1, is_ref=0)=NULL;
  4. public $user = (refcount=1, is_ref=0)=''
  5. }
  6. m: (refcount=2, is_ref=0)=class User {
  7. public $name = (refcount=1, is_ref=0)='sdfsdfs';
  8. public $model = (refcount=1, is_ref=0)=NULL;
  9. public $user = (refcount=1, is_ref=0)=''
  10. }
复制代码
xdebug给出的is_ref=0。refcount与普通变量一直。但是类的赋值是引用赋值。
  1. $user = new User();
  2. $user->user = $user;
  3. $user->name = 'sdfsdfs';
  4. xdebug_debug_zval('user');
  5. unset($user);
复制代码
上面内容输出:
  1. user: (refcount=2, is_ref=0)=class User { public $name = (refcount=1, is_ref=0)='sdfsdfs'; public $user = (refcount=2, is_ref=0)=... }
复制代码
这里由于类的赋值是引用赋值,索引也构成了一个递归操纵,如许也会跟数组一样出现内存走漏的环境。对以下代码个自行100次
  1. $user = new User();
  2. $user->user = $user;
  3. $user->name = 'sdfsdfs';
  4. xdebug_debug_zval('user');
  5. unset($user);$user = new User(); $user->user = new Order(); $user->name = 'sdfsdfs'; xdebug_debug_zval('user'); unset($user);
复制代码
第一段代码前后内存差1408 byte. 第二段代码差208 byte。
总结
以上就是这篇文章的全部内容了,渴望本文的内容对各人的学习或者工作具有一定的参考学习代价,如果有疑问各人可以留言交流,谢谢各人对草根技术分享的支持。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作