是否可以查看两个数组变量是否指向相同的内存位置?(它们是相同的数组)
8 回答
实际上,这是可以做到的。通过 php 扩展。
文件:config.m4
PHP_ARG_ENABLE(test, 是否开启测试扩展支持, [ --enable-test 开启测试扩展支持]) 如果测试“$PHP_TEST”=“是”;然后 AC_DEFINE(HAVE_TEST, 1, [启用测试扩展]) PHP_NEW_EXTENSION(测试,test.c,$ext_shared) 菲
文件:php_test.h
#ifndef PHP_TEST_H #define PHP_TEST_H 1 #define PHP_TEST_EXT_VERSION "1.0" #define PHP_TEST_EXT_EXTNAME “测试” PHP_FUNCTION(getaddress4); PHP_FUNCTION(获取地址); extern zend_module_entry test_module_entry; #define phpext_test_ptr &test_module_entry #万一
文件:test.c
#ifdef HAVE_CONFIG_H #include "config.h" #万一 #include "php.h" #include "php_test.h" ZEND_BEGIN_ARG_INFO_EX(func_args, 1, 0, 0) ZEND_END_ARG_INFO() 静态函数入口测试函数[] = { PHP_FE(getaddress4, func_args) PHP_FE(getaddress, func_args) {空,空,空} }; zend_module_entry test_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #万一 PHP_TEST_EXT_EXTNAME, 测试功能, 无效的, 无效的, 无效的, 无效的, 无效的, #if ZEND_MODULE_API_NO >= 20010901 PHP_TEST_EXT_VERSION, #万一 STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_TEST ZEND_GET_MODULE(测试) #万一 PHP_FUNCTION(getaddress4) { zval *var1; zval *var2; zval *var3; zval *var4; 字符 r[500]; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "aaaa", &var1, &var2, &var3, &var4) == FAILURE) { RETURN_NULL(); } sprintf(r, "\n%p - %p - %p - %p\n%p - %p - %p - %p", var1, var2, var3, var4, Z_ARRVAL_P(var1), Z_ARRVAL_P(var2) , Z_ARRVAL_P(var3), Z_ARRVAL_P(var4) ); RETURN_STRING(r, 1); } PHP_FUNCTION(获取地址) { zval *var; 字符 r[100]; if(zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "a", &var) == FAILURE) { RETURN_NULL(); } sprintf(r, "%p", Z_ARRVAL_P(var)); RETURN_STRING(r, 1); }
然后,您所要做的就是对其进行 phpize、配置和制作。将“extension=/path/to/so/file/modules/test.so”添加到您的 php.ini 文件中。最后,重新启动 Web 服务器,以防万一。
<?php $x = 数组(“123”=>“123”); $w = $x; $y = $x; $z = &$x; var_dump(getaddress4($w,$x,$y,$z)); var_dump(getaddress($w)); var_dump(getaddress($x)); var_dump(getaddress($y)); var_dump(getaddress($z)); ?>
返回(至少对我来说,你的内存地址可能会不同)
细绳 ' 0x9efeb0 - 0x9efeb0 - 0x9ef8c0 - 0x9efeb0 0x9efee0 - 0x9f0010 - 0x9ed790 - 0x9efee0'(长度=84) 字符串“0x9efe0”(长度=8) 字符串“0x9f0010”(长度=8) 字符串“0x9ed790”(长度=8) 字符串“0x9efe0”(长度=8)
感谢 Artefacto 指出这一点,但我的原始代码是按值传递数组,因此重新创建了包含引用的数组,并给你错误的内存值。此后,我更改了代码以强制所有参数通过引用传递。这将允许引用、数组和对象在不受 php 引擎干扰的情况下传入。$w/$z 是一回事,但 $w/$x/$y 不是。旧代码实际上显示了引用损坏以及当所有变量都传入与多次调用同一函数时内存地址会更改或匹配的事实。这是因为 PHP 在进行多次调用时会重用相同的内存。比较原始函数的结果是没有用的。新代码应该可以解决这个问题。
仅供参考 - 我正在使用 php 5.3.2。
PHP 中的引用是一种通过不同名称访问相同变量内容的方法。它们不像 C 指针;例如,您不能使用它们执行指针运算,它们不是实际的内存地址,等等。
结论:不,你不能
来自:http ://www.php.net/manual/en/language.references.whatare.php
你的问题实际上有点误导。“指向同一个内存位置”和“是同一个数组”(对我来说这意味着是一个引用,至少在 PHP 中)不是一回事。
内存位置是指指针。指针在 PHP 中不可用。引用不是指针。
无论如何,如果您想检查是否$b
实际上是 的参考$a
,这是您可以获得的最接近实际答案的方法:
function is_ref_to(&$a, &$b) {
if (is_object($a) && is_object($b)) {
return ($a === $b);
}
$temp_a = $a;
$temp_b = $b;
$key = uniqid('is_ref_to', true);
$b = $key;
if ($a === $key) $return = true;
else $return = false;
$a = $temp_a;
$b = $temp_b;
return $return;
}
$a = array('foo');
$b = array('foo');
$c = &$a;
$d = $a;
var_dump(is_ref_to($a, $b)); // false
var_dump(is_ref_to($b, $c)); // false
var_dump(is_ref_to($a, $c)); // true
var_dump(is_ref_to($a, $d)); // false
var_dump($a); // is still array('foo')
function check(&$a,&$b){
// perform first basic check, if both have different values
// then they're definitely not the same.
if($a!==$b)return false;
// backup $a into $c
$c=$a;
// get some form of opposite to $a
$a=!$a;
// compare $a and $b, if both are the same thing,
// this should be true
$r=($a===$b);
// restore $a from $c
$a=$c;
// return result
return $r;
}
$a=(object)array('aaa'=>'bbb'); $b=&$a;
echo check($a,$b) ? 'yes' : 'no'; // yes
$c='aaa'; $d='aaa';
echo check($c,$d) ? 'yes' : 'no'; // no
$e='bbb'; $f='ccc';
echo check($e,$f) ? 'yes' : 'no'; // no
“检查”功能是在 2 分钟左右创建的。它假定如果您更改引用的值,第二个引用也将具有新添加的值。此函数仅适用于变量。您可以将它用于常量值、函数返回(除非通过引用)等。
编辑:在测试过程中,我有一些最初的困惑。我一直重复使用相同的变量名($a 和 $b),这导致所有条件都是“是”。原因如下:
$a='aaa'; $b=&$a; // a=aaa b=aaa
$a='ccc'; $b='ddd'; // a=ddd b=ddd <= a is not ccc!
为了纠正这个问题,我给了他们一个不同的名字:
$a='aaa'; $b=&$a; // a=aaa b=aaa
$c='ccc'; $d='ddd'; // c=ccc d=ddd <= c is now correct
编辑:为什么答案是“是”而不是“否”
PHP 不会通过脚本来显示指针信息(也不是指针操作等)。但是,它确实允许使用引用运算符“&”完成别名变量(引用)。特征通常出现在指针中,这解释了一般的混淆。也就是说,指针不是别名。
但是,如果我们看到原始问题,人们想知道 $a 是否与 $b 相同,而不是在内存中找到 $a(或 $b)的位置。前面的要求适用于引用和指针,而后面的要求只适用于指针。
首先,你的问题很模糊。它可能意味着几个不同的东西:
- 变量是否具有相同的内容?为此,您可以使用
===
. - 变量是否在内部使用相同的内存?
- 这些变量是否在同一个参考集中?即,给定两个变量,
$a
如果$b
我改变$a
,它会改变$b
吗?
第二个答案的答案不容易确定。Jeremy Walton 的回答有一个重大问题——他的函数按值接收,所以如果你传递一个引用,你就会强制分离并获得一个新的临时值的地址。您可以使函数通过引用接收参数,但是您会遇到相反的问题——如果您传递了一个值(refcount >= 2),您也会强制分离。
更重要的是,第二个问题是一个无关紧要的内部细节。考虑以下脚本:
$a = 1;
$b = $a; //addresses of $a and $b are the same
function force_sep(&$a) { }
force_sep($b);
//force_sep is a no-op, but it forced a separation; now addresses are not equal
所以重要的问题是第三个问题。不幸的是,没有直接的方法来确定这一点。这已被多次要求;参见例如这个请求。
但是,有几个选项:
- 您可以接收变量的名称并在符号表中查找它。这也是
xdebug_debug_zval
比有缺陷的更有趣的地方debug_zval_dump
。这是EG(active_symbol_table)
对简单变量的简单查找(但如果您想包含对象属性和尺寸等,则会变得更加复杂),这也将允许您为第二个问题实施解决方案。 - 您还可以修改 Jeremy Walton 的答案以使函数通过引用接收(您需要一个 arginfo 结构)并同时接收两个值。同时接收它们可以避免由于重用内存地址而导致误报(尽管是否有问题取决于函数的用法;另一方面,Jeremy Walton的函数在接收引用时总是会遇到这个问题-我可以详细说明如有必要,请参阅我在他的回答下的评论)。
- netcoder 的回答虽然很老套,但也有效。这个想法是通过引用接收两个变量,改变一个,看看另一个是否改变,最后恢复值。
PHP中的参考比较
我知道这个问题很老,但这仍然是相关的——这就是我最终来到这里的原因。可能有几种方法可以对此进行测试,但我想出了其他几种方法。
PHP 7.4 引用相等测试
ReflectionReference 为数组元素提供了一个参考 id:
function is_same(&$a, &$b): bool {
$_ = [ &$a, &$b ];
return
\ReflectionReference::fromArrayElement($_, 0)->getId() ===
\ReflectionReference::fromArrayElement($_, 1)->getId();
}
PHP 版本 5、7 和 8
此函数将根据 PHP 序列化检测循环引用这一事实来发现实际引用。缺点是对于大型数组,它将暂时需要内存和时间来序列化数据。对于大数组,最好使用下面的实用数组相等测试。
function is_same(&$a, &$b) {
$_ = [ &$a, &$b ];
// PHP >= 7.4
if (\class_exists(\ReflectionReference::class)) {
return
\ReflectionReference::fromArrayElement($_, 0)->getId() ===
\ReflectionReference::fromArrayElement($_, 1)->getId();
}
// Faster, for objects
if (\is_object($a) && \is_object($b) && $a === $b) return true;
// Stop if they aren't identical, this is much faster.
if ($a !== $b) return false;
// Resources can't be serialized
if (\is_resource($a) && \is_resource($b) && "".$a === "".$b) return true;
// Serialization supports references, so we utilize that
return \substr(\serialize($_), -5) === 'R:2;}';
}
内存友好的 PHP < 7.4 数组引用检查
这个测试应该在不浪费太多内存的情况下完成任务。一个副作用是 PHP 使用写时复制来节省数组的内存 - 所以当这个函数附加到数组时,它将触发该机制。
function array_is_same(array &$a, array &$b): bool {
// Fastest test first
if ($a !== $b) {
return false;
}
// Then the reference test
try {
// Need a unique key
while (
array_key_exists($key = '#! '.mt_rand(PHP_INT_MIN, PHP_INT_MAX), $a) ||
array_key_exists($key, $b)
);
$a[$key] = true;
return isset($b[$key]);
} finally {
// cleanup
unset($a[$key], $b[$key]);
}
}
function var_name(&$ref){
foreach($GLOBALS as $key => $val){
if($val === $ref) return $key;
}
}
这是未经测试的,但我所知道的 php,var 在加载到系统中时被添加到 GLOBALS 中,所以它们相同的第一次出现应该是原始 var,但如果你有 2 个变量完全相同我'我不确定它会如何反应
$a["unqiue-thing"] = 1;
if($b["unique-thing"] == 1) // a and b are the same