Is it possible to see if two array variables point to the same memory location? (they are the same array)
Actually, this can be done. Through a php extension.
File: 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
File: 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
File: 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); }
Then all you have to do is phpize it, config it, and make it. Add a "extension=/path/to/so/file/modules/test.so" to your php.ini file. And finally, restart the web server, just in case.
<?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)); ?>
Returns(at least for me, your memory addresses will probably be different)
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)
Thanks to Artefacto for pointing this out, but my original code was passing the arrays by value, so thereby was recreating arrays including the referenced-one, and giving you bad memory values. I have since changed the code to force all params to be passed by reference. This will allow references, arrays, and object, to be passed in unmolested by the php engine. $w/$z are the same thing, but $w/$x/$y are not. The old code, actually showed the reference breakage and the fact that the memory addresses would change or match when all variables were passed in vs multiple calls to the same function. This was because PHP would reuse the same memory when doing multiple calls. Comparing the results of the original function would be useless. The new code should fix this problem.
FYI - I'm using php 5.3.2.
getaddress()
function body in here –
Vida Z_ARRVAL_P
instead of breaking the zval abstraction (and yes, sometimes the field names do change, like in 5.3 refcount and is_ref). –
Banas $x = array(); $z = $x;
, then $z
and $x
would indeed point to the same zval *
until a separation would be forced (copy-on-write mechanism). Your example is slightly different -- when you do $w = $x;
, $w
and $x
point to the same zval *
(refcount=2, is_ref=0). But when you do $z =& $x;
, you're forcing a separation because $z
and $x
are references and $w
is not and a zval cannot have two different values for the is_ref
field. So the 1st zval ($w/$x) is copied and its refcount decremented. The new copy ($z/$x; after) has refcount set to 2, and is_ref=1. –
Banas zval *
is on the stack (note the declaration), not only that, if they were linked going in though, why do they NOT have the same HashTable memory address, if not the same array. Even their bucket addresses are completely different. And from TESTING, your statement although should be accurate, is completely false, see above code. –
Broadwater zval *
being on the stack is relevant here; what matters is the value of var
, not its address. What I said was true. Let me explain. After your assignments, you have 2 zvals -- $y/$w with refcount=2, is_ref=0 and $x/$z with refcount=2, is_ref=1. The thing is, since your function receives by value, for $y and $w the refcount is simply incremented when entering the function (and you see 3). For $x and $z, there has to be a separation, because the function must end up with a non-reference. So you see refcount=1, is_ref=0 inside the function. –
Banas zval *
's because their address values are different because they are different variables. The OPs question was not that the variables are the same, but if the arrays are the same in memory, the only way to really verify that is to check the array to which it is being pointed to. In my second validation, you can see that $x/$z point to one array and $w/$y point to a completely new array. Where as $w/$y have the same zval, $x/$z do not have the same zval. –
Broadwater zval *
or checking the HashTable *
is completely indifferent -- hash tables are not shared between zvals. But zval *
pointers are shared between several variables. Symbol tables are hash tables that store store, as values, this pointers, the keys being the names of the variables. –
Banas References in PHP are a means to access the same variable content by different names. They are not like C pointers; for instance, you cannot perform pointer arithmetic using them, they are not actual memory addresses, and so on.
Conclusion: No, you can not
From: http://www.php.net/manual/en/language.references.whatare.php
Your question is actually a bit misleading. "point to the same memory location" and "are the same array" (which to me means is a reference to, at least in PHP) are not the same thing.
Memory locations refers to pointers. Pointers are not available in PHP. References are not pointers.
Anyway, if you want to check if $b
is in fact a reference of $a
, this is the closest you can get to an actual answer:
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')
pointer arithmetic
. PHP does NOT support this, so it's NOT possible. And this script does NOT prove that the same memory location is used. As Hamish already explained.. @stereofrog, your link has also nothing to do with memory locations. It only shows whether 2 variables are aliases or not. This does NOT prove anything about their memory locations, in fact.. aliases can use different memory blocks. –
Jamie Is it possible to see if two array variables point to the same memory location?
without the (they are the same array)-remark
. And then the answer is; no
. –
Jamie you might also take php5 objects (which are pointers)
, no, they are not pointers. –
Jamie uniqid
? –
Querulous 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
The function "check" was created in 2 mins or so. It assumes that if you change a reference's value, a second reference would have the newly add value as well. This function works on variables only. You can use it against constant value, function returns (unless by reference) etc.
Edit: During testing, I had some initial confusion. I kept reusing the same variable names ($a and $b) which resulted in all the conditionals being "yes". Here's why:
$a='aaa'; $b=&$a; // a=aaa b=aaa
$a='ccc'; $b='ddd'; // a=ddd b=ddd <= a is not ccc!
To correct the issue, I gave them a different name:
$a='aaa'; $b=&$a; // a=aaa b=aaa
$c='ccc'; $d='ddd'; // c=ccc d=ddd <= c is now correct
Edit: Why the answer is "yes" and not "no"
PHP does not reveal pointer information through scripting (neither pointer manipulation etc). However, it does allow alias variables (references), done by using the reference operator '&'. Feature is typically found in pointers, which explains the general confusion. That said, pointers are not aliases.
However, if we see the original question, the person wanted to know if $a is the same as $b, not where in the memory $a (or $b) is found. Whereas the earlier requirement applies to both references and pointers, the later one only applies to pointers.
First, your question is vague. It can mean several different things:
- Do the variables have the same content? For this, you can use
===
. - Do the variables use internally the same memory?
- Are these variables in the same reference set? I.e., given two variables,
$a
and$b
, if I change$a
, will it change$b
?
The answer to the second answer is not easy to determine. Jeremy Walton's answer has one significant problem -- his function receives by value, so if you pass it a reference, you force a separation and get the address of a new temporary value. You could make the function receive the parameter by reference, but then you'd have the opposite problem -- if you passed a value (with refcount >= 2), you would also force a separation.
More importantly, the second question is an irrelevant internal detail. Consider the following script:
$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
So the important question is the third one. Unfortunately, there is no straightforward way to determine this. This has been requested several times; see e.g. this request.
However, there are a few options:
- You could to receive the name of the variable and look it up in the symbol table. This is also what makes
xdebug_debug_zval
much more interesting than the flaweddebug_zval_dump
. This is a simple lookup inEG(active_symbol_table)
for simple variables (but would get more complex if you wanted to include object properties and dimensions etc.), and this would also allow you to implement a solution for the 2nd question. - You could also modify Jeremy Walton's answer to make the function receive by reference (you'd need an arginfo structure) and receive the two values at the same time. Receiving them at the same time can avoid false positives due to reused memory addresses (though whether it's a problem depends on the usage of the function; on the other hand, Jeremy Walton's function always suffers from this problem when receiving references -- I can elaborate on this if necessary, but see my comment under his answer).
- netcoder's answer, although hackish, also works. The idea is to receive two variables by reference, change one, and see if the other one changed, restoring the values in the end.
Reference comparison in PHP
I know the question is old, but this is still relevant - which is why I ended up here. There are probably several ways to test this, but I came up with a couple of other methods.
PHP 7.4 reference equality test
ReflectionReference provides a reference id for array elements:
function is_same(&$a, &$b): bool {
$_ = [ &$a, &$b ];
return
\ReflectionReference::fromArrayElement($_, 0)->getId() ===
\ReflectionReference::fromArrayElement($_, 1)->getId();
}
PHP version 5, 7 and 8
This function will spot an actual reference, by relying on the fact that PHP serialization detects circular references. The downside is that for big arrays it will temporary need memory and time to serialize the data. For big arrays it may be better to use the pragmatic array equality test below.
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;}';
}
Memory friendly PHP < 7.4 array reference checking
This test should do the deed without wasting too much memory. A side effect is that PHP uses copy-on-write to save memory on arrays - so when this function appends to the array, it will trigger that mechanism.
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;
}
}
This is untested but what i know of php, vars are added to the GLOBALS as they are are loaded into the system, so the first occurance where they are identical should be the original var, but if you have 2 Variables Exactly the same i'm not sure how it would react
===
operator checks for type, but not for reference. Which means if you have $a = 1; $b = 1; $c = &$a
, then the following is true: $a === $b === $c
, even if $b
is not a reference. The only ways to know if a variable is a reference is to a) look at the code; or b) modify it and see if the original variable changes. As for $GLOBALS, it doesn't have anything to do with this. A reference declared in a function is still a reference, but will not be part of $GLOBALS. –
Faustena "aaa"==="aaa"
is true (eventhough they are different constant values). As to the GLOBALS idea you mentioned, that only works when in the global scope (afaik). –
Chancy $o1=new stdClass(); $o2=new stdClass(); var_dump($o1===$o2);
<= that results in false. $s1="aaa"; $s2="aaa"; var_dump($s1===$s2);
<= that results in true. –
Chancy $a["unqiue-thing"] = 1;
if($b["unique-thing"] == 1) // a and b are the same
$a
as an array, then do $b = $a
, $b will in fact be reference $b
- as in, no extra memory is assigned. If you then do $b[] = "new item"
only then does PHP copy the entire array and make the change. You can test this by checking memory usage of a large array if you like. memory usage is only bumped up after changing the second array, not during assignment. Try it. –
Countdown © 2022 - 2024 — McMap. All rights reserved.