如何判断两个数组变量是否指向同一内存位置?(它们是同一个数组)
如何判断两个数组变量是否指向同一内存位置?(它们是同一个数组)
实际上,这可以通过php扩展完成。
文件: config.m4
PHP_ARG_ENABLE(test, 是否启用测试扩展支持, [ --enable-test 启用测试扩展支持])
if test "$PHP_TEST" = "yes"; then AC_DEFINE(HAVE_TEST, 1, [启用TEST扩展]) PHP_NEW_EXTENSION(test, test.c, $ext_shared) fi
文件: php_test.h
#ifndef PHP_TEST_H #define PHP_TEST_H 1
#define PHP_TEST_EXT_VERSION "1.0" #define PHP_TEST_EXT_EXTNAME "test"
PHP_FUNCTION(getaddress4); PHP_FUNCTION(getaddress);
extern zend_module_entry test_module_entry; #define phpext_test_ptr &test_module_entry
#endif
文件: test.c
#ifdef HAVE_CONFIG_H #include "config.h" #endif
#include "php.h" #include "php_test.h"
ZEND_BEGIN_ARG_INFO_EX(func_args, 1, 0, 0) ZEND_END_ARG_INFO()
static function_entry test_functions[] = { PHP_FE(getaddress4, func_args) PHP_FE(getaddress, func_args) {NULL, NULL, NULL} };
zend_module_entry test_module_entry = { #if ZEND_MODULE_API_NO >= 20010901 STANDARD_MODULE_HEADER, #endif PHP_TEST_EXT_EXTNAME, test_functions, NULL, NULL, NULL, NULL, NULL, #if ZEND_MODULE_API_NO >= 20010901 PHP_TEST_EXT_VERSION, #endif STANDARD_MODULE_PROPERTIES };
#ifdef COMPILE_DL_TEST ZEND_GET_MODULE(test) #endif
PHP_FUNCTION(getaddress4) { zval *var1; zval *var2; zval *var3; zval *var4; char r[500]; if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "aaaa", &var1, &var2, &var3, &var4) == FAILURE) { RETURN_NULL(); }PHP_FUNCTION(getaddress) { zval *var; char 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); }以上函数用于获取变量在内存中的地址,其中:
使用方法:
Z_ARRVAL_P
而不是打破zval抽象(有时字段名称确实会更改,例如在5.3中的refcount和is_ref)。 - Artefacto$x = array(); $z = $x;
,那么 $z
和 $x
确实会指向同一个 zval *
,直到强制分离(写时复制机制)。你的例子略有不同--当你执行 $w = $x;
时,$w
和 $x
指向同一个 zval *
(refcount=2,is_ref=0)。但是当你执行 $z =& $x;
时,你正在强制分离,因为 $z
和 $x
是引用,而 $w
不是,一个 zval 不能对 is_ref
字段有两个不同的值。所以第一个 zval($w/$x)被复制并且它的 refcount 被减少。新的副本($z/$x; after)的 refcount 设置为 2,并且 is_ref=1。 - Artefacto在PHP中,引用是一种通过不同的名称访问相同变量内容的方式。它们不像C指针;例如,您不能使用它们执行pointer arithmetic,它们不是实际的内存地址等。
结论:不,你不能
来源:http://www.php.net/manual/en/language.references.whatare.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')
uniqid
会更加简洁,不是吗? - Pacerier 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
“check”函数是在大约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)在内存中的位置。虽然之前的要求适用于引用和指针,但后一种要求只适用于指针。
我知道这个问题已经很老了,但它仍然很相关,这就是为什么我来到这里的原因。可能有几种测试方法,但我想出了另外几种方法。
ReflectionReference为数组元素提供了引用ID:
function is_same(&$a, &$b): bool {
$_ = [ &$a, &$b ];
return
\ReflectionReference::fromArrayElement($_, 0)->getId() ===
\ReflectionReference::fromArrayElement($_, 1)->getId();
}
该函数将依赖于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使用写时复制技术来节省数组所占用的内存 - 因此当这个函数向数组追加内容时,它将触发该机制。
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]);
}
}
===
来判断。
- 这些变量是否在内部使用相同的内存?
- 这些变量是否在同一引用集中?也就是说,如果给定两个变量$a
和$b
,如果我更改了$a
,那么$b
会发生变化吗?$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的答案虽然有些hackish,但也可以工作。其思想是通过引用接收两个变量,更改一个变量,并查看另一个变量是否更改,在最后恢复值。function var_name(&$ref){
foreach($GLOBALS as $key => $val){
if($val === $ref) return $key;
}
}
这是未经测试的,但根据我所知的 PHP,变量在加载到系统中时会被添加到 GLOBALS 中,因此它们相同的第一次出现应该是原始变量,但如果您有两个完全相同的变量,我不确定它会如何反应
===
运算符检查类型,但不检查引用。这意味着如果你有 $a = 1; $b = 1; $c = &$a
,那么以下是正确的:$a === $b === $c
,即使 $b
不是一个引用。知道变量是否是引用的唯一方法是 a) 查看代码;或 b) 修改它并查看原始变量是否更改。至于 $GLOBALS
,它与此无关。在函数中声明的引用仍然是引用,但不会成为 $GLOBALS
的一部分。 - netcoder"aaa" === "aaa"
是正确的(即使它们是不同的常量值)。至于你提到的 GLOBALS 想法,只有在全局范围内才能起作用(据我所知)。 - Christian $a["unqiue-thing"] = 1;
if($b["unique-thing"] == 1) // a and b are the same
$a
创建为一个数组,然后执行$b = $a
,那么$b将实际上是引用 $b
- 也就是说,没有额外的内存被分配。如果你随后执行$b[] = "new item"
,那么PHP才会复制整个数组并进行修改。你可以通过检查大型数组的内存使用情况来测试这一点。内存使用情况只有在修改第二个数组时才会增加,而不是在赋值过程中增加。你可以试试看。 - Hamish