2つの配列変数が同じメモリ位置を指しているかどうかを確認することは可能ですか?(それらは同じ配列です)
8 に答える
実際、これは可能です。php拡張を介して。
ファイル:config.m4
PHP_ARG_ENABLE(test、テスト拡張サポートを有効にするかどうか、[--enable-testテスト拡張サポートを有効にする]) テスト"$PHP_TEST"="yes"の場合; それから 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、 ヌル、 ヌル、 ヌル、 ヌル、 ヌル、 #if ZEND_MODULE_API_NO> = 20010901 PHP_TEST_EXT_VERSION、 #endif STANDARD_MODULE_PROPERTIES }; #ifdef COMPILE_DL_TEST ZEND_GET_MODULE(テスト) #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)); ?>
戻り値(少なくとも私にとっては、メモリアドレスはおそらく異なるでしょう)
ストリング ' 0x9efeb0-0x9effe0-0x9ef8c0-0x9efeb0 0x9efee0-0x9f0010-0x9ed790-0x9efee0'(長さ= 84) 文字列'0x9efee0'(長さ= 8) 文字列'0x9f0010'(長さ= 8) 文字列'0x9ed790'(長さ= 8) 文字列'0x9efee0'(長さ= 8)
これを指摘してくれたArtefactoに感謝しますが、私の元のコードは配列を値で渡していたため、参照された配列を含む配列を再作成し、メモリ値が不良でした。その後、すべてのパラメーターが参照によって渡されるようにコードを変更しました。これにより、参照、配列、およびオブジェクトを、phpエンジンによって無秩序に渡されるようになります。$ w / $ zは同じものですが、$ w / $ x /$yは違います。古いコードは、実際には参照の破損と、同じ関数への複数の呼び出しに対してすべての変数が渡されたときにメモリアドレスが変更または一致するという事実を示していました。これは、PHPが複数の呼び出しを行うときに同じメモリを再利用するためです。元の関数の結果を比較しても意味がありません。新しいコードでこの問題が修正されるはずです。
参考までに-私はphp5.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分程度で作成されました。参照の値を変更すると、2番目の参照にも新しく追加された値があると想定されます。この関数は変数でのみ機能します。定数値、関数の戻り値(参照による場合を除く)などに対して使用できます。
編集:テスト中に、私はいくつかの最初の混乱がありました。同じ変数名($aと$b)を再利用し続けたため、すべての条件が「yes」になりました。理由は次のとおりです。
$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)が見つかったかではありません。前の要件は参照とポインターの両方に適用されますが、後の要件はポインターにのみ適用されます。
まず、あなたの質問は曖昧です。それはいくつかの異なることを意味する可能性があります:
- 変数の内容は同じですか?これには、を使用できます
===
。 - 変数は内部的に同じメモリを使用していますか?
- これらの変数は同じ参照セットにありますか?つまり、2つの変数が与えられた場合、変更する
$a
と変更されますか?$b
$a
$b
2番目の答えに対する答えを決定するのは簡単ではありません。Jeremy Waltonの答えには1つの重要な問題があります。彼の関数は値で受け取るため、参照を渡すと、強制的に分離して新しい一時値のアドレスを取得します。関数に参照によってパラメーターを受け取らせることもできますが、逆の問題が発生します。値を渡した場合(refcount> = 2)、強制的に分離することもできます。
さらに重要なことに、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
したがって、重要な質問は3番目の質問です。残念ながら、これを判断する簡単な方法はありません。これは何度か要求されています。たとえば、このリクエストを参照してください。
ただし、いくつかのオプションがあります。
- 変数の名前を受け取り、それをシンボルテーブルで調べることができます。これは
xdebug_debug_zval
、欠陥のあるものよりもはるかに興味深いものでもありdebug_zval_dump
ます。これは、単純EG(active_symbol_table)
な変数の単純なルックアップです(ただし、オブジェクトのプロパティやディメンションなどを含める場合は、より複雑になります)。これにより、2番目の質問のソリューションを実装することもできます。 - Jeremy Waltonの回答を変更して、関数が参照で受信し(arginfo構造体が必要)、2つの値を同時に受信するようにすることもできます。それらを同時に受信すると、再利用されたメモリアドレスによる誤検知を回避できます(ただし、問題があるかどうかは関数の使用法によって異なりますが、一方、Jeremy Waltonの関数は、参照を受信するときに常にこの問題に悩まされます-詳しく説明できます必要に応じてこれについてですが、彼の答えの下にある私のコメントを参照してください)。
- ネットコーダーの答えは、ハック的ですが、機能します。アイデアは、参照によって2つの変数を受け取り、一方を変更し、もう一方が変更されたかどうかを確認して、最終的に値を復元することです。
PHPでの参照比較
私は質問が古いことを知っていますが、これはまだ関連しています-それが私がここにたどり着いた理由です。これをテストする方法はおそらくいくつかありますが、他にもいくつかの方法を考え出しました。
PHP7.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について知っていることですが、変数はシステムにロードされるときにGLOBALSに追加されるため、それらが同一である最初の出現は元の変数である必要がありますが、2つの変数がある場合はまったく同じi ' mそれがどのように反応するかわからない
$a["unqiue-thing"] = 1;
if($b["unique-thing"] == 1) // a and b are the same