内郚からPHPを孊習したす。 ズノァル

この蚘事は、私が珟圚ロシア語に翻蚳しおいるPHP Internals Bookの Zvals章に基づいおいたす[ 1 ]。 この本の䞻な目的は、PHPの拡匵機胜を䜜成したいCプログラマヌですが、むンタヌプリタヌの内郚ロゞックに぀いお説明しおいるので、PHP開発者にずっお圹立぀ず確信しおいたす。 この蚘事では、基本的な理論のみを残したしたが、これはすべおの開発者に明らかなはずですPHPやCにも粟通しおいたせん。 資料のより完党なプレれンテヌションに぀いおは、本を参照しおください。

タスクは泚意を匕くこずです。 次のコヌドを実行した結果はどうなりたすか
$obj1 = new StdClass(); $obj2 = new StdClass(); $obj1->value = 1; $obj2->value = 1; function f1($o) { $o = 100; } function f2($o) { $o->value = 100; } f1($obj1); f2($obj2); var_dump($obj1); var_dump($obj2); 


答え
オブゞェクトstdClass11{["value"] => int1}
オブゞェクトstdClass21{["value"] => int100}

答えを正確に定矩し、その理由を説明できる堎合は、おそらくこの蚘事から新しいこずを孊ぶこずはないでしょう。さもなければ、この蚘事を読んで知識を深めおください。

基本構造

PHPの基本的なデヌタ構造はzval「Zend value」の略です。 各zvalはそれ自䜓にいく぀かのフィヌルドを栌玍し、そのうちの2぀はこの倀の倀ずタむプです。 PHPは動的型付け蚀語であり、したがっお倉数の型はコンパむル時ではなく実行時にのみ認識されるため、これが必芁です。 さらに、倉数の型はzvalの有効期間䞭に倉曎できたす。぀たり、以前に敎数ずしお保存されおいたzvalは埌で文字列を含むこずができたす。

倉数の型は敎数ラベル型タグ、笊号なし文字ずしお保存されたす。 ラベルは、PHPで䜿甚可胜な8぀のデヌタ型に察応する8぀の倀のいずれかを取るこずができたす。 これらの倀は、 IS_TYPE圢匏の定数を䜿甚しお割り圓おる必芁がありたす。 たずえば、 IS_NULLはNULLデヌタ型にIS_STRINGし、 IS_STRINGは文字列にIS_STRINGしたす。

zvalue_value

倉数の実際の倀は、次のように定矩されるナニオンデヌタ型「ナニオン」、以䞋ではナニオンたたはナニオンずいう甚語を䜿甚したすに栌玍されたす。
 typedef union _zvalue_value { long lval; double dval; struct { char *val; int len; } str; HashTable *ht; zend_object_value obj; } zvalue_value; 

組合の抂念に粟通しおいない人のための小さな説明。 Unionは、さたざたなタむプの耇数のデヌタメンバヌを定矩したすが、ナニオンで定矩された倀からは垞に1぀の倀しか䜿甚できたせん。 たずえば、 value.lvalがデヌタメンバヌvalue.lvalに割り圓おられた堎合、 value.lvalのみを䜿甚しおデヌタにアクセスできたす。他のデヌタメンバヌぞのアクセスは受け入れられず、プログラムの予期しない動䜜に぀ながる可胜性がありたす。 これは、ナニオンがすべおのメンバヌのデヌタを同じメモリ領域に保存し、アクセスしおいる名前に基づいお倀を異なる方法で解釈するためです。 ナニオンに割り圓おられたメモリのサむズは、その最倧デヌタメンバヌのサむズに察応したす。

zval-sを䜿甚する堎合、特別なタグタむプタグが䜿甚されたす。これにより、珟圚ナニオンに栌玍されおいるデヌタのタむプを刀別できたす。 APIに移る前に、PHPでサポヌトされおいるデヌタ型ずその保存方法を確認したしょう。

最も単玔なデヌタ型はIS_NULLですIS_NULLであるため、倀を栌玍しないでください。

数倀を栌玍するために、PHPは2぀のタむプを衚したす IS_LONGおよびIS_DOUBLEでdouble dvalそれぞれlong lvalおよびdouble dval䜿甚したす。 最初は敎数を保存するために䜿甚され、2番目は浮動小数点数のために䜿甚されたす。

longデヌタ型に぀いお知っおおくべきこずがいく぀かありたす。 たず、笊号付き敎数です。぀たり、正の倀ず負の倀を含むこずができたすが、このデヌタ型はビット挔算には適しおいたせん。 次に、longはプラットフォヌムごずにサむズが異なりたす。32ビットシステムではサむズが32ビットたたは4バむトですが、64ビットシステムではサむズが4たたは8バむトになりたす。 Unixシステムでは通垞8バむトのサむズですが、64ビットバヌゞョンのWindowsでは4バむトしか䜿甚したせん。

このため、long型の特定の倀に䟝存しないでください。 longデヌタ型に栌玍できる最小倀ず最倧倀は、 LONG_MINおよびLONG_MAXで䜿甚でき、この型のサむズはSIZEOF_LONGマクロを䜿甚しお決定できたす sizeof(long)ずは異なり、このマクロは#ifディレクティブでも䜿甚できたす。

doubleデヌタ型は、浮動小数点数を栌玍するためのものであり、通垞IEEE-754仕様に埓っお、8バむトのサむズを持っおいたす。 この圢匏の詳现に぀いおはここでは説明したせんが、少なくずもこの型の粟床は制限されおおり、倚くの堎合、期埅しおいる倀を正確に保存しないこずに泚意しおください。

ブヌル倉数はIS_BOOLフラグを䜿甚し、倀0 停および1 真ずしおlong valフィヌルドに栌玍されたす。 2぀の倀のみがこの型を䜿甚するため、理論的にはより小さい型たずえば、zend_boolを䜿甚しおも十分ですが、zvalue_valueはナニオンであり、最倧デヌタメンバヌに察応するメモリサむズが割り圓おられ、よりコンパクトな倉数を適甚したすブヌル倀の堎合、メモリは節玄されたせん。 したがっお、この堎合はlvalが再利甚されたす。

行 IS_STRING は、構造struct {char *val; int len; } str; struct {char *val; int len; } str; ぀たり、文字列は、文字列char *および文字列int敎数長ぞのポむンタずしお栌玍されたす。 PHPの文字列は、NULバむト\ 0を含むこずができ、バむナリセヌフであるために、長さを明瀺的に保存する必芁がありたす。 ただし、これにもかかわらず、PHPで䜿甚される文字列は、文字列の長さの匕数をずらないラむブラリ関数ずの互換性を確保するために、NULで終了するバむトで終了したすが、文字列の最埌にnullバむトが芋぀かるず想定しおいたす もちろん、このような堎合、文字列はバむナリセヌフではなくなり、れロバむトが最初に珟れるたで切り捚おられたす。 たずえば、倚くのファむルシステム関連の関数ずlibcのほずんどの文字列関数は同様に動䜜したす。

文字列の長さはUnicode文字の数ではなくバむト単䜍で枬定され、れロバむトを含むべきではありたせん。぀たり、文字列foo長さは、栌玍に4バむトが䜿甚されるにもかかわらず3です。 sizeofを䜿甚しお文字列の長さを決定する堎合、1を枛算する必芁がありたす strlen("foo") == sizeof("foo") - 1 。

理解するこずが非垞に重芁です。文字列の長さは、long型たたは他の類䌌の型ではなく、int型に栌玍されたす。 これは、文字列の長さを2147483647バむト2ギガバむトに制限する歎史的なアヌティファクトです。 より倧きな行は、オヌバヌフロヌを匕き起こしたす長さが負になりたす。

残りの3぀のタむプは、衚面的にのみ蚀及されたす。

配列はIS_ARRAYラベルを䜿甚し、 HashTable *htデヌタHashTable *ht栌玍されたす。 HashTableデヌタ構造の仕組みに぀いおは、別の蚘事で説明しおいたす。

オブゞェクト IS_OBJECT はzend_object_value objデヌタzend_object_value obj䜿甚したす。これは、「オブゞェクトハンドル」実際のデヌタの怜玢に䜿甚される敎数IDず、オブゞェクトの動䜜を決定する「オブゞェクトハンドラヌ」のセットで構成されたす。 PHPのクラスずオブゞェクトのシステムに぀いおは、「クラスずオブゞェクト」の章で説明したす。

リ゜ヌス IS_RESOURCE は、倀の怜玢に䜿甚される䞀意のIDも栌玍するため、オブゞェクトに䌌おいたす。 このIDは、長いlvalメンバヌに保存されたす。 リ゜ヌスに぀いおは、ただ曞かれおいない察応する章で説明したす。

小蚈を芁玄するず、䜿甚可胜なすべおのタむプラベルずそれに察応する倀ストアをリストした衚が以䞋にありたす。
タむプタグ保管堎所
IS_NULLnone
IS_BOOLlong lval
IS_LONGlong lval
IS_DOUBLEdouble dval
IS_STRINGstruct { char *val; int len; } str
IS_ARRAYHashTable *ht
IS_OBJECTzend_object_value obj
IS_RESOURCElong lval

zval

次に、zvalデヌタ構造がどのように芋えるかを芋おみたしょう。

 typedef struct _zval_struct { zvalue_value value; zend_uint refcount__gc; zend_uchar type; zend_uchar is_ref__gc; } zval; 

既に述べたように、zvalには倀ずその型を栌玍するためのメンバヌが含たれおいたす。 倀は、䞊蚘のzvalue_valueナニオンに栌玍されたす。 タむプはzend_uchar type保存されたす。 さらに、この構造には、名前が__gcで終わる2぀の远加のプロパティが含たれおいたす。これらのプロパティは、ガベヌゞコレクションメカニズムによっお䜿甚されたす。 これらのプロパティに぀いおは、次のセクションで詳しく説明したす。

メモリ管理

zvalデヌタ構造は2぀の圹割を果たしたす。 たず、前のセクションで説明したように、デヌタずそのタむプを保存したす。 第二にこれに぀いおは珟圚のセクションで説明したす、メモリ内の倀を効率的に管理するために䜿甚されたす。

このセクションでは、リンクカりントずコピヌオンラむトの抂念に぀いお説明したす。

倀ず参照のセマンティクス

PHPでは、参照の䜿甚を明瀺的に芁求しない限り、すべおの倀には垞に倀のセマンティクスがありたす。 これは、関数に倀を枡すずき、および割り圓お操䜜䞭に、倀の2぀の異なるコピヌで䜜業するこずを意味したす。 これを確認するのに圹立぀䟋がいく぀かありたす。
 <?php $a = 1; $b = $a; $a++; //  $a    1, $b   : var_dump($a, $b); // int(2), int(1) function inc($n) { $n++; } $c = 1; inc($c); //   $c     $n   —    var_dump($c); // int(1) 

䞊蚘の䟋は非垞に単玔で明癜ですが、これはあらゆる堎所に適甚される基本的なルヌルであるこずを理解するこずが重芁です。 オブゞェクトにも適甚されたす
 <?php $obj = (object) ['value' => 1]; function fnByVal($val) { //     ,     object  integer $val = 100; } function fnByRef(&$ref) { $ref = 100; } // ,    ,   $obj,       — : fnByVal($obj); var_dump($obj); // stdClass(value => 1),  fnByVal     fnByRef($obj); var_dump($obj); // int(100) 

PHP 5では、オブゞェクトは参照によっお自動的に枡されるこずがよくありたすが、䞊蚘の䟋はそうではないこずを瀺しおいたす。 倀が枡される関数は、枡される倉数の倀を倉曎できたせん。リンクが枡される関数のみがこれを行うこずができたす。

オブゞェクトは参照枡しされたかのように動䜜したすが、これは事実です。 倉数に異なる倀を割り圓おるこずはできたせんが、オブゞェクトのプロパティを倉曎するこずはできたす。 これは、オブゞェクトの倀がオブゞェクトの「実際のデヌタ」の怜玢に䜿甚されるIDであるため可胜です。 倀枡しのセマンティクスでは、このIDを別のIDに倉曎したり、倉数の型を倉曎したりするこずはできたせんが、オブゞェクトの「実際のデヌタ」を倉曎するこずはできたせん。

䞊蚘の䟋を少し倉曎しおみたしょう。

 <?php $obj = (object) ['value' => 1]; function fnByVal($val) { //      ,       $val->value = 100; } var_dump($obj); // stdClass(value => 1) fnByVal($obj); var_dump($obj); // stdClass(value => 100),  fnByVal      

リ゜ヌスに぀いおも、デヌタの怜玢に䜿甚できるIDのみが保存されるため、同じこずが蚀えたす。 したがっお、倀枡しのセマンティクスでは、zvalのIDたたはタむプを倉曎するこずはできたせんが、リ゜ヌスデヌタを倉曎するこずはできたせんたずえば、ファむル内の䜍眮をシフトするため。

リンクカりントずコピヌオンラむト

䞊蚘の内容に぀いお少し考えるず、PHPは膚倧な数のコピヌ操䜜を実行する必芁があるずいう結論に達するでしょう。 関数に倉数を枡すたびに、その倀をコピヌする必芁がありたす。 これは敎数型たたは倍粟床型のデヌタでは問題にならないかもしれたせんが、1000䞇個の倀を含む配列を関数に枡すず想像しおください。 関数が呌び出されるたびに䜕癟䞇もの倀をコピヌするず、蚱容できないほど遅くなりたす。

これを避けるために、PHPはコピヌオンラむトパラダむムを䜿甚したす。 Zvalは倚くの倉数/関数/などで共有できたすが、読み取りにzvalデヌタが䜿甚されおいる堎合に限りたす。 誰かがzvalデヌタを倉曎したいず思うずすぐに、倉曎が適甚される前にコピヌされたす。

1぀のzvalを耇数の堎所で䜿甚できるため、PHPは瞬間を刀断できるはずです。誰もzvalコヌドを䜿甚しおそれを削陀したせん占有しおいるメモリを解攟したす。 PHPはこれを単玔な参照カりントにしたす。 ここでの「リンク」はPHP &指定されたリンクではなく、誰か倉数、関数などがこのzvalを䜿甚しおいるこずを瀺すむンゞケヌタヌにすぎないこずに泚意しおください。 このようなリンクの数はrefcountず呌ばれ、 refcount__gc zvalのデヌタメンバヌに栌玍されたす。

これがどのように機胜するかを理解するために、䟋を芋おみたしょう。

 <?php $a = 1; // $a = zval_1(value=1, refcount=1) $b = $a; // $a = $b = zval_1(value=1, refcount=2) $c = $b; // $a = $b = $c = zval_1(value=1, refcount=3) $a++; // $b = $c = zval_1(value=1, refcount=2) // $a = zval_2(value=2, refcount=1) unset($b); // $c = zval_1(value=1, refcount=1) // $a = zval_2(value=2, refcount=1) unset($c); // zval_1 ,   refcount=0 // $a = zval_2(value=2, refcount=1) 

ここでのロゞックは単玔ですリンクが远加されるず、 refcountの倀が1぀増加し、リンクが削陀されるず、 refcount枛少したす。 refcountの倀が0に達するず、zvalが削陀されたす。

確かに、このメ゜ッドは埪環参照の堎合には機胜したせん。

 <?php $a = []; // $a = zval_1(value=[], refcount=1) $b = []; // $b = zval_2(value=[], refcount=1) $a[0] = $b; // $a = zval_1(value=[0 => zval_2], refcount=1) // $b = zval_2(value=[], refcount=2) // refcount zval_2    //     zval_1 $b[0] = $a; // $a = zval_1(value=[0 => zval_2], refcount=2) // $b = zval_2(value=[0 => zval_1], refcount=2) // refcount zval_1    //     zval_2 unset($a); // zval_1(value=[0 => zval_2], refcount=1) // $b = zval_2(value=[0 => zval_1], refcount=2) // refcount zval_1 ,  zval //          zval_2 unset($b); // zval_1(value=[0 => zval_2], refcount=1) // zval_2(value=[0 => zval_1], refcount=1) // refcount zval_2 ,  //          zval_1 

䞊蚘のコヌドが起動されるず、2぀のzvalが倉数を介しおアクセスできなくなりたすが、それらは盞互に参照するため、メモリ内にただ存圚する状況になりたす。 これは、リンクカりントの問題の兞型的な䟋です。

この問題を解決するために、PHPには別のガベヌゞコレクションメカニズム-埪環コレクタヌがありたす。 埪環コレクタヌはリンクカりントメカニズムずは異なりPHP拡匵機胜の開発者に察しお透過的であるため、これを無芖できたす。 このトピックに興味がある堎合は、このアルゎリズムに぀いお説明しおいるPHPドキュメントを参照しおください。

PHPリンクの別の機胜䞊蚘で説明したものではなく&$varずしお定矩されおいるものを怜蚎する必芁がありたす。 zvalがPHPリンクずしお䜿甚されるこずを瀺すために、 is_ref__gcフラグがzval構造䜓でis_ref__gcれたす。

is_ref=1堎合、これは倉曎前にzvalをコピヌしおはならないずいうシグナルであり、代わりにzval倀を倉曎する必芁がありたす。

 <?php $a = 1; // $a = zval_1(value=1, refcount=1, is_ref=0) $b =& $a; // $a = $b = zval_1(value=1, refcount=2, is_ref=1) $b++; // $a = $b = zval_1(value=2, refcount=2, is_ref=1) //   is_ref=1 PHP   zval //       

䞊蚘の䟋では、リンクを䜜成する前に、倉数$aのrefcount=1が蚭定されおいたす。 次に、耇数のリンクカりントを持぀同様の䟋を考えおみたしょう。

 <?php $a = 1; // $a = zval_1(value=1, refcount=1, is_ref=0) $b = $a; // $a = $b = zval_1(value=1, refcount=2, is_ref=0) $c = $b // $a = $b = $c = zval_1(value=1, refcount=3, is_ref=0) $d =& $c; // $a = $b = zval_1(value=1, refcount=2, is_ref=0) // $c = $d = zval_2(value=1, refcount=2, is_ref=1) // $d   $c,  **  $a and $b,  // zval    .     //  zval  is_ref=0   is_ref=1. $d++; // $a = $b = zval_1(value=1, refcount=2, is_ref=0) // $c = $d = zval_2(value=2, refcount=2, is_ref=1) //       2  zvals $d++  //  $a  $b (  ). 

ご芧のずおり、 is_ref=0ぞのリンクを䜜成するずき、c is_ref=0およびrefcount>1はコピヌが必芁です。 同様に、倀枡しのコンテキストでis_ref=1およびrefcount>1でzvalを䜿甚する堎合、コピヌ操䜜が必芁です。 このため、PHPリンクを䜿甚するず、通垞コヌドが遅くなりたす。 PHPのほずんどすべおの関数は、倀枡しのセマンティクスを䜿甚するため、倀is_ref=1 zvalを取埗するずきにコピヌを䜜成したす。

おわりに

この蚘事では、PHP Internals BookのZvalsの章を絞りたした。 PHP開発者に圹立぀資料のみを残しお、拡匵機胜の開発に関連する倚くのテキストをカットしようずしたしたそうしないず、蚘事の量が3倍になりたす。 PHPの拡匵機胜の開発の問題をさらに詳しく調べるこずに興味がある堎合は、 本たたは私の翻蚳を参照しおください。 珟時点では、Zvalsのヘッドのみが翻蚳されおいたすが、私は仕事を続けおいたす。 近い将来、ハッシュテヌブルずクラスに関する興味深い章を取り䞊げたす。



[ 1 ]本の翻蚳は著者の蚱可を埗お行われおいたすが、非公匏です。 ここで私の翻蚳を読むこずができたす romka.gitbooks.io/php-internals-book-ru 、ここで翻蚳を助ける github.com/romka/phpinternalsbook-ru

Source: https://habr.com/ru/post/J226707/


All Articles