ホームページ バックエンド開発 PHPチュートリアル PHPパラメータ受け渡し原理の詳細な分析

PHPパラメータ受け渡し原理の詳細な分析

Jul 25, 2016 am 09:13 AM

PHP 拡張機能を作成する場合、パラメーター (つまり、zend_parse_parameters に渡される変数) を自由にする必要はないようです。 例:

  1. PHP_FUNCTION(test)

  2. {
  3. char* str;
  4. int str_len;
  5. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, " s" ,&str , &str_len) == FAILURE) {

  6. RETURN_FALSE;
  7. }
  8. php_printf(str);

  9. // free(str) は必要ありません
  10. }
正常に動作するコード
をコピーします: test("Hello World"); // Hello World を出力します。 ここでは、テスト関数でのメモリ リークを心配する必要はありません。PHP は、パラメータの保存に使用されるこれらの変数を自動的にリサイクルします。

では、php はどのように行うのでしょうか?この問題を説明するには、PHP がパラメータを渡す方法を調べる必要があります。

EG (argument_stack) の紹介 簡単に言うと、パラメーターの保存に特に使用されるスタックは、argument_stack という名前で PHP の EG に保存されます。関数呼び出しが発生するたびに、php は受信パラメータを EG (argument_stack) にプッシュします。関数呼び出しが終了すると、EG (argument_stack) はクリアされ、次の関数呼び出しを待ちます。

EG (argument_stack) の構造体と目的に関して、php5.2 と 5.3 の実装ではいくつかの違いがあります。この記事では主に 5.2 を例に挙げ、5.3 以降の変更点については後ほど説明します。

PHPパラメータ受け渡し原理の詳細な分析

上の図は 5.2 の argument_stack の大まかな図で、シンプルで明瞭に見えます。このうちスタックの上下はNULL固定となります。関数によって受け取られたパラメータは、左から右の順序でスタックにプッシュされます。最後に追加の長い値がプッシュされ、スタック内のパラメータの数 (上の図では 10) が示されることに注意してください。

argument_stack にプッシュされるパラメータは何ですか?実際、これらは zval 型のポインタです。 それらが指す zva は、CV 変数、is_ref=1 の変数、または定数または定数文字列である可能性があります。

EG (argument_stack) は、php5.2 では zend_ptr_stack タイプとして具体的に実装されています。

typedef struct _zend_ptr_stack {
    int top // スタック内の現在の要素の数
  1. int max // スタックに格納されている要素の最大数
  2. void **elements; stack
  3. void * *top_element; // スタックの先頭
  4. } zend_ptr_stack;
  5. argument_stack を初期化する argument_stack の初期化作業は、PHP が特定のリクエストを処理する前に行われます。より正確には、PHP インタープリターの起動プロセス中に行われます。
init_executor 関数内に次の 2 行が見つかりました。

zend_ptr_stack_init(&EG(argument_stack));

zend_ptr_stack_push(&EG(argument_stack), (void *) NULL);
    コードをコピー
  1. これらの 2 行は、初期化 EG(ar Gument_stack)、その後NULL をプッシュします。 EG はグローバル変数であるため、zend_ptr_stack_init が実際に呼び出される前は、EG (argument_stack) 内のすべてのデータはすべて 0 になります。
zend_ptr_stack_init の実装は非常に簡単です。 + gt; max = PTR_STACK_BLOCK_SIZE; // スタック サイズは 64 に初期化されます stack->top = 0; // 現在の要素数は 0 です}

argument_stack が初期化されたら、 NULL がプッシュされます。ここで詳しく説明する必要はありませんが、この NULL には実際には意味がありません。

NULL がスタックにプッシュされた後、argument_stack 全体の実際のメモリ配分は次のようになります。
  1. パラメータをスタックにプッシュする 最初の NULL をプッシュした後、別のパラメーターがスタックにプッシュされると、argument_stack で次のアクションが発生します。 スタック->トップ++; *(スタック->top_element++) = パラメーター;
  2. 単純な PHP コードを使用して問題を説明します。
function foo( $str ){
print_r(123);}foo("hello world");

コードをコピーPHPパラメータ受け渡し原理の詳細な分析

上記のコードは、foo を呼び出すときに文字列定数を渡します。したがって、実際にスタックにプッシュされるのは、ストレージ「hello world」を指す zval です。 vld を使用して、コンパイルされたオペコードを表示します。

  1. line # * op fetch ext return オペランド
  2. ------------------------------------------------- - -------------------------------------------------
  3. 3 0 > NOP
  4. 6 1 SEND_VAL OP1[ IS_CONST (458754) 'hello world' ]
  5. 2 DO_FCALL 1 OP1[ IS_CONST (458752) 'foo' ]
  6. 15 3 > RETURN OP1[ IS_CONST (0) 1 ]
Copy コード

SEND_VAL 命令が実際に行うことは、「hello world」を argument_stack にプッシュすることです。

  1. int ZEND_SEND_VAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

  2. {
  3. zval *valptr, *value;
  4. value = &opline- &g t;op1.u.定数 ;

  5. ALLOC_ZVAL(valptr);
  6. INIT_PZVAL_COPY(valptr, value);
  7. if (!0) {
  8. zval_copy_ctor(valptr);
  9. }
  10. // スタックにプッシュし、valptr は格納先をポイントします。 hello world zval

  11. zend_ptr_stack_push(&EG(argument_stack), valptr);
  12. ……
  13. }
コードをコピーします
プッシュが完了した後の argument_stack は次のとおりです:

PHPパラメータ受け渡し原理の詳細な分析3

パラメータの数 前述したように、実際にはすべてのパラメータをスタックにプッシュするだけではありません。 PHP は、パラメーターの数を表す追加の数値もプッシュします。この作業は、SEND_XXX コマンド中には発生しません。実際、関数を実際に実行する前に、PHP はパラメーターの数をスタックにプッシュします。

引き続き上記の例を使用すると、DO_FCALL 命令を使用して foo 関数を呼び出します。 foo を呼び出す前に、php は argument_stack の最後のブロックを自動的に埋めます。

  1. static int zend_do_fcall_common_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS)
  2. {
  3. ……
  4. // 2 つの値を argument_stack にプッシュします
  5. // 1 つはパラメータの数です (つまり、opline->extended_value)
  6. // 1 つはスタックの最上位にある識別子 NULL です
  7. zend_ptr_stack_2_push(&EG(argument_stack), (void *)(zend_uintptr_t)opline->extended_value, NULL);
  8. if (EX(function_state).function->type = = ZEND_INTERNAL_FUNCTION) {
  9. ……
  10. }
  11. else if (EX(function_state).function->type == ZEND_USER_FUNCTION) {
  12. ……
  13. // foo 関数を呼び出す
  14. zend_execute(EG(active_op_array) TSRMLS_CC);
  15. }
  16. else { /* ZEND_OVERLOADED_FUNCTION +/ used for foo 呼び出しの argument_stack 全体が完了しました。
  17. パラメータを取得する 上の例を続けます。 foo 関数を詳しく見て、foo のオペコードがどのようなものかを確認してください。
  18. line # * op fetch ext return オペランド
  19. ------------------------------------------------- - -------------------------------------------------
  20. 3 0 > RECV OP1[ IS_CONST (0) 1 ]
4 1 SEND_VAL OP1[ IS_CONST (5) 123 ]
2 DO_FCALL 1 OP1[ IS_CONST (459027) 'print_r' ] 5 3 > RETURN OP1[ IS_CONST (0) ) null ]

PHPパラメータ受け渡し原理の詳細な分析コードをコピー

最初の命令は RECV で、文字通りスタック内のパラメータを取得するために使用されます。実はSEND_VALとRECVはなんとなく対応している気がします。 SEND_VAL は各関数呼び出しの前に、RECV が関数内で実行されます。完全に対応しているとは言えませんが、実はRECV命令は必ずしも必要ではありません。 RECV は、ユーザー定義関数が呼び出された場合にのみ生成されます。私たちが作成する拡張関数と PHP に付属する組み込み関数には RECV がありません。

各 SEND_VAL と RECV は 1 つのパラメーターのみを処理できることに注意してください。つまり、パラメータの受け渡し処理で複数のパラメータがある場合、複数の SEND_VAL と複数の RECV が生成されます。これは非常に興味深いトピックにつながります。パラメーターの受け渡しとパラメーターの取得の順序は何ですか?

答えは、SEND_VAL がスタック上のパラメータを左から右にプッシュするのに対し、RECV はパラメータを左から右に取得するということです。

  1. static int ZEND_RECV_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

  2. {
  3. ……//param はスタックの先頭から順にパラメータを取得します
  4. if (zend_ptr_stack_get_arg(ar) g_num , (void * *) ¶m TSRMLS_CC)==FAILURE) {
  5. ……
  6. } else {
  7. zend_free_op free_res;
  8. zval **var_ptr;
  9. // パラメータを確認します

  10. zend_verify_arg_type((zend_function; *) EG( active_op_array), arg_num, *param TSRMLS_CC);
  11. var_ptr = get_zval_ptr_ptr(&opline->result, EX(Ts), &free_res, BP_VAR_W);
  12. // パラメータを取得します
  13. if (PZVAL_IS_REF(*param)) {
  14. zend_assign_to_variable_reference( var_ptr, param TSRMLS_CC);
  15. } else {
  16. zend_receive(var_ptr, *param TSRMLS_CC);
  17. }
  18. }
  19. ZEND_VM_NEXT_OPCODE();

  20. }& lt;/p>
コピー コード
zend_assign_to_variable_reference と zend_receive は「パラメータの取得」を完了します。 「パラメータを取得する」ということは、実際には何をするのかを理解するのが簡単ではありません。

最終的には、「パラメーターの取得」は、現在の関数の実行中にこのパラメーターを「シンボル テーブル」に追加することです。これは、具体的には EG (current_execute_data)->symbol_table に対応します。この例では、RECV が完了した後、関数本体のsymbol_table にシンボル 'str' があり、その値は "hello world" です。

しかし、RECV はパラメーターを読み取るだけで、スタック上でポップ操作を実行しないため、argument_stack はまったく変更されていません。

PHPパラメータ受け渡し原理の詳細な分析5

argument_stack をクリーンアップ foo 内の print_r も関数呼び出しであるため、スタック プッシュ --> スタック クリア操作も発生します。したがって、print_r が実行される前の argument_stack は次のようになります。

PHPパラメータ受け渡し原理の詳細な分析6

print_r が実行された後、argument_stack は foo が RECV を終了したばかりの状態に戻ります。

print_r を呼び出す具体的なプロセスは、この記事の焦点では​​ありません。私たちが関心があるのは、PHP が foo を呼び出した後に argument_stack をどのようにクリーンアップするかということです。

上記の do_fcall コード スニペットからわかるように、クリーニング作業は zend_ptr_stack_clear_multiple によって完了します。

  1. static inline void zend_ptr_stack_clear_multiple(TSRMLS_D)
  2. {
  3. void **p = EG(argument_stack).top_element-2;
  4. // スタックの先頭に保存されているパラメータの数を取得します
  5. int delete_count = ( int)(zend_uintptr_t ) *p;
  6. EG(argument_stack).top -= (delete_count+2);
  7. // (--delete_count>=0) の間、上から下にクリーンアップします
  8. zval *q = * (zval * *)(--p);
  9. *p = NULL;
  10. zval_ptr_dtor(&q);
  11. }
  12. EG(argument_stack).top_element = p;
  13. }
コードをコピー
ここではスタック内の zval ポインタ、zval_ptr_dtor が使用されます。 zval_ptr_dtor は、refcount を 1 だけデクリメントします。refcount が 0 に減少すると、変数を格納しているメモリ領域が実際にリサイクルされます。

この記事の例では、foo が呼び出された後、「hello world」の zval ステータスが保存されます。

    value "hello world"
  1. refcount 1
  2. type 6
  3. is_ref 0
コードをコピー

refcount は 1 のみなので、zval_ptr_dtor は実際にメモリから「hello world」を破棄します。

スタック削除後のargument_stackのメモリステータスは次のとおりです:

PHPパラメータ受け渡し原理の詳細な分析7

上の図の argument_stack は、初期化されたばかりのときと同じであることがわかります。この時点で、argument_stack は次の関数呼び出しに対応できるようになります。

記事冒頭の質問に戻ります... なぜfree(str)が必要ないのでしょうか? argument_stack を理解すると、この問題を簡単に理解できるようになります。

str は、実際に zval に「hello world」が格納されているメモリアドレスを指しているためです。拡張関数が次のとおりであると仮定します。

  1. PHP_FUNCTION(test)

  2. {
  3. char* str;
  4. int str_len;
  5. if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, " s" ,&str , &str_len) == FAILURE) {

  6. RETURN_FALSE;
  7. }
  8. str[0] = 'H';

  9. }
コードをコピーしてください
電話

  1. $a = "hello world";
  2. test($a);
  3. echo $a;
コードをコピー
すると「Hello world」と出力されます。 test を呼び出すとき、$a の参照は渡しませんが、実際の効果は test(&$a) と同等です。

簡単に言うと、CV 配列であろうとargument_stack であろうと、メモリ内には $a のコピーが 1 つだけ存在します。 zend_parse_parameters は関数実行用のデータのコピーをコピーしません。実際、そうすることはできません。したがって、関数が完了したときに $a が他の場所で使用されていない場合、PHP は argument_stack をクリーンアップするときに $a を解放するのに役立ちます。他のコードで使用されている場合、手動で解放することはできません。そうしないと、$a のメモリ領域が破壊されてしまいます。

拡張関数の作成に使用されるすべての変数が PHP によって自動的にリサイクルされるわけではないことに注意してください。だから、自由になるときは、優しくしないでください:)


このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

PHPのカール:REST APIでPHPカール拡張機能を使用する方法 PHPのカール:REST APIでPHPカール拡張機能を使用する方法 Mar 14, 2025 am 11:42 AM

PHPクライアントURL(CURL)拡張機能は、開発者にとって強力なツールであり、リモートサーバーやREST APIとのシームレスな対話を可能にします。尊敬されるマルチプロトコルファイル転送ライブラリであるLibcurlを活用することにより、PHP Curlは効率的なexecuを促進します

Codecanyonで12の最高のPHPチャットスクリプト Codecanyonで12の最高のPHPチャットスクリプト Mar 13, 2025 pm 12:08 PM

顧客の最も差し迫った問題にリアルタイムでインスタントソリューションを提供したいですか? ライブチャットを使用すると、顧客とのリアルタイムな会話を行い、すぐに問題を解決できます。それはあなたがあなたのカスタムにより速いサービスを提供することを可能にします

PHPにおける後期静的結合の概念を説明します。 PHPにおける後期静的結合の概念を説明します。 Mar 21, 2025 pm 01:33 PM

記事では、PHP 5.3で導入されたPHPの後期静的結合(LSB)について説明し、より柔軟な継承を求める静的メソッドコールのランタイム解像度を可能にします。 LSBの実用的なアプリケーションと潜在的なパフォーマ

JSON Web Tokens(JWT)とPHP APIでのユースケースを説明してください。 JSON Web Tokens(JWT)とPHP APIでのユースケースを説明してください。 Apr 05, 2025 am 12:04 AM

JWTは、JSONに基づくオープン標準であり、主にアイデンティティ認証と情報交換のために、当事者間で情報を安全に送信するために使用されます。 1。JWTは、ヘッダー、ペイロード、署名の3つの部分で構成されています。 2。JWTの実用的な原則には、JWTの生成、JWTの検証、ペイロードの解析という3つのステップが含まれます。 3. PHPでの認証にJWTを使用する場合、JWTを生成および検証でき、ユーザーの役割と許可情報を高度な使用に含めることができます。 4.一般的なエラーには、署名検証障害、トークンの有効期限、およびペイロードが大きくなります。デバッグスキルには、デバッグツールの使用とロギングが含まれます。 5.パフォーマンスの最適化とベストプラクティスには、適切な署名アルゴリズムの使用、有効期間を合理的に設定することが含まれます。

フレームワークセキュリティ機能:脆弱性から保護します。 フレームワークセキュリティ機能:脆弱性から保護します。 Mar 28, 2025 pm 05:11 PM

記事では、入力検証、認証、定期的な更新など、脆弱性から保護するためのフレームワークの重要なセキュリティ機能について説明します。

フレームワークのカスタマイズ/拡張:カスタム機能を追加する方法。 フレームワークのカスタマイズ/拡張:カスタム機能を追加する方法。 Mar 28, 2025 pm 05:12 PM

この記事では、フレームワークにカスタム機能を追加し、アーキテクチャの理解、拡張ポイントの識別、統合とデバッグのベストプラクティスに焦点を当てています。

PHPのCurlライブラリを使用してJSONデータを含むPOSTリクエストを送信する方法は? PHPのCurlライブラリを使用してJSONデータを含むPOSTリクエストを送信する方法は? Apr 01, 2025 pm 03:12 PM

PHP開発でPHPのCurlライブラリを使用してJSONデータを送信すると、外部APIと対話する必要があることがよくあります。一般的な方法の1つは、Curlライブラリを使用して投稿を送信することです。

See all articles