Is it possible to see if two array variables point to the same memory location? (they are the same array)
-
Why is this tagged zend-engine by someone else?Harmen– Harmen2010-11-05 23:45:05 +00:00Commented Nov 5, 2010 at 23:45
-
6@Harmen Because for a qualified answer that goes beyond a mere Yes or No, you will have to have some knowledge of how the Zend Engine, e.g. the thing that drives PHP, handles variables and memory.Gordon– Gordon2010-11-06 00:34:41 +00:00Commented Nov 6, 2010 at 0:34
-
Why do you want to do that? Maybe we can help if you explain your concrete problem.c0rnh0li0– c0rnh0li02010-11-23 18:25:15 +00:00Commented Nov 23, 2010 at 18:25
-
2c0rnh0li0 - He just wants to do that. So what if he doesn't have a good reason. It's understand and fixing the issue that is important, not fixing someone's way of thinking.Christian– Christian2010-11-25 14:57:11 +00:00Commented Nov 25, 2010 at 14:57
-
1Actually sometimes what is required is fixing someones thinking. Programmers spend a lot of time reinventing wheels that others have solved. Not sure if thats the case here, but often is.Toby Allen– Toby Allen2010-11-25 21:43:06 +00:00Commented Nov 25, 2010 at 21:43
8 Answers
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.
20 Comments
getaddress() function body in here$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.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
3 Comments
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')
10 Comments
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.you might also take php5 objects (which are pointers), no, they are not pointers. 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.
Comments
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,
$aand$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_zvalmuch 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.
Comments
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]);
}
}
Comments
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
9 Comments
=== 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."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). $a["unqiue-thing"] = 1;
if($b["unique-thing"] == 1) // a and b are the same
9 Comments
$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.