使用内存引用比较PHP数组

时间:2010-11-05 23:33:41

标签: php arrays php-internals

是否可以查看两个数组变量是否指向同一个内存位置? (它们是相同的阵列)

8 个答案:

答案 0 :(得分:14)

实际上,这可以做到。通过php扩展。

文件:config.m4

PHP_ARG_ENABLE(test, whether to enable test Extension support, [ --enable-test   Enable test ext support])

if test "$PHP_TEST" = "yes"; then
  AC_DEFINE(HAVE_TEST, 1, [Enable TEST Extension])
  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();
    }
    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(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);
}

然后你所要做的就是phpize它,配置它,然后制作它。在php.ini文件中添加“extension = / path / to / so / file / modules / test.so”。最后,重新启动Web服务器,以防万一。

<?php
  $x = array("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));
?>

返回(至少对我来说,你的内存地址可能会有所不同)

string '
0x9efeb0 - 0x9effe0 - 0x9ef8c0 - 0x9efeb0
0x9efee0 - 0x9f0010 - 0x9ed790 - 0x9efee0' (length=84)

string '0x9efee0' (length=8)

string '0x9f0010' (length=8)

string '0x9ed790' (length=8)

string '0x9efee0' (length=8)

感谢Artefacto指出这一点,但我的原始代码是按值传递数组,因此重新创建了包含引用数组的数组,并为您提供了错误的内存值。我已经改变了代码以强制所有参数通过引用传递。这将允许引用,数组和对象在php引擎中不受干扰地传递。 $ w / $ z是相同的,但是$ w / $ x / $ y不是。旧代码实际上显示了引用破坏以及当所有变量传递给多个调用同一函数时内存地址将改变或匹配的事实。这是因为PHP在进行多次调用时会重用相同的内存。比较原始函数的结果将是无用的。新代码应该解决这个问题。

仅供参考 - 我使用的是php 5.3.2。

答案 1 :(得分:9)

你的问题实际上有点误导。 “指向相同的内存位置”和“是相同的数组”(这对我来说意味着是一个引用,至少在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')

答案 2 :(得分:8)

PHP中的引用是一种通过不同名称访问相同变量内容的方法。它们不像C指针;例如,您无法使用它们执行pointer arithmetic,它们不是实际的内存地址,依此类推。

结论:不,你不能

来自:http://www.php.net/manual/en/language.references.whatare.php

答案 3 :(得分:3)

        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不会通过脚本(指针操作等)显示指针信息。 但是,它确实允许使用引用运算符'&amp;'完成的别名变量(引用)。 功能通常在指针中找到,这解释了一般的混淆。 也就是说,指针不是别名。

但是,如果我们看到原始问题,那么该人想知道$ a是否与$ b相同,而不是在内存中找到$ a(或$ b)。虽然早期的要求适用于引用和指针,但后者仅适用于指针。

答案 4 :(得分:2)

首先,你的问题很模糊。它可能意味着几件事:

  • 变量是否具有相同的内容?为此,您可以使用===
  • 变量是否在内部使用相同的内存?
  • 这些变量是否在同一参考集中?即,给定两个变量$a$b,如果我更改$a,它会更改$b吗?

第二个答案的答案不容易确定。 Jeremy Walton的答案有一个重要问题 - 他的函数按值接收,所以如果你传递一个引用,你强制分离并得到一个新临时值的地址。你可以让函数通过引用接收参数,但是你会遇到相反的问题 - 如果你传递了一个值(使用refcount&gt; = 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

所以重要的问题是第三个问题。不幸的是,没有直接的方法来确定这一点。已多次要求这样做;见例如这request

但是,有几个选择:

  • 您可以接收变量的名称并在符号表中查找。这也是使xdebug_debug_zval比有缺陷的debug_zval_dump更有趣的原因。对于简单变量,这是EG(active_symbol_table)中的简单查找(但如果要包含对象属性和维度等,则会变得更复杂),这也可以让您为第二个问题实现解决方案。
  • 您还可以修改Jeremy Walton的答案,使函数通过引用接收(您需要一个arginfo结构)并同时接收这两个值。同时接收它们可以避免由于重用的内存地址引起的误报(虽然它是否是一个问题取决于函数的使用;另一方面,Jeremy Walton的函数始终会遇到这个问题接收参考资料 - 如有必要,我可以详细说明,但请参阅我的评论。
  • netcoder的答案,尽管是hackish,但也有效。我们的想法是通过引用接收两个变量,更改一个,然后查看另一个变量,最后恢复这些值。

答案 5 :(得分:0)

function var_name(&$ref){
    foreach($GLOBALS as $key => $val){
       if($val === $ref) return $key;
    }
}

这是未经测试的,但我所知道的php,vars被添加到GLOBALS中,因为它们被加载到系统中,因此它们相同的第一次出现应该是原始的var,但是如果你有两个变量就是同样我不确定它会如何反应

答案 6 :(得分:0)

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]);
  }
}

答案 7 :(得分:-1)

 $a["unqiue-thing"] = 1;
 if($b["unique-thing"] == 1) // a and b are the same