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

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 までご連絡ください。
PHP:サーバー側のスクリプト言語の紹介PHP:サーバー側のスクリプト言語の紹介Apr 16, 2025 am 12:18 AM

PHPは、動的なWeb開発およびサーバー側のアプリケーションに使用されるサーバー側のスクリプト言語です。 1.PHPは、編集を必要とせず、迅速な発展に適した解釈言語です。 2。PHPコードはHTMLに組み込まれているため、Webページの開発が簡単になりました。 3。PHPプロセスサーバー側のロジック、HTML出力を生成し、ユーザーの相互作用とデータ処理をサポートします。 4。PHPは、データベースと対話し、プロセスフォームの送信、サーバー側のタスクを実行できます。

PHPとWeb:その長期的な影響を調査しますPHPとWeb:その長期的な影響を調査しますApr 16, 2025 am 12:17 AM

PHPは過去数十年にわたってネットワークを形成しており、Web開発において重要な役割を果たし続けます。 1)PHPは1994年に発信され、MySQLとのシームレスな統合により、開発者にとって最初の選択肢となっています。 2)コア関数には、動的なコンテンツの生成とデータベースとの統合が含まれ、ウェブサイトをリアルタイムで更新し、パーソナライズされた方法で表示できるようにします。 3)PHPの幅広いアプリケーションとエコシステムは、長期的な影響を促進していますが、バージョンの更新とセキュリティの課題にも直面しています。 4)PHP7のリリースなど、近年のパフォーマンスの改善により、現代の言語と競合できるようになりました。 5)将来的には、PHPはコンテナ化やマイクロサービスなどの新しい課題に対処する必要がありますが、その柔軟性とアクティブなコミュニティにより適応性があります。

なぜPHPを使用するのですか?利点と利点が説明されましたなぜPHPを使用するのですか?利点と利点が説明されましたApr 16, 2025 am 12:16 AM

PHPの中心的な利点には、学習の容易さ、強力なWeb開発サポート、豊富なライブラリとフレームワーク、高性能とスケーラビリティ、クロスプラットフォームの互換性、費用対効果が含まれます。 1)初心者に適した学習と使用が簡単。 2)Webサーバーとの適切な統合および複数のデータベースをサポートします。 3)Laravelなどの強力なフレームワークを持っています。 4)最適化を通じて高性能を達成できます。 5)複数のオペレーティングシステムをサポートします。 6)開発コストを削減するためのオープンソース。

神話を暴く:PHPは本当に死んだ言語ですか?神話を暴く:PHPは本当に死んだ言語ですか?Apr 16, 2025 am 12:15 AM

PHPは死んでいません。 1)PHPコミュニティは、パフォーマンスとセキュリティの問題を積極的に解決し、PHP7.xはパフォーマンスを向上させます。 2)PHPは最新のWeb開発に適しており、大規模なWebサイトで広く使用されています。 3)PHPは学習しやすく、サーバーはうまく機能しますが、タイプシステムは静的言語ほど厳格ではありません。 4)PHPは、コンテンツ管理とeコマースの分野で依然として重要であり、エコシステムは進化し続けています。 5)OpcacheとAPCを介してパフォーマンスを最適化し、OOPと設計パターンを使用してコードの品質を向上させます。

PHP対Pythonの議論:どちらが良いですか?PHP対Pythonの議論:どちらが良いですか?Apr 16, 2025 am 12:03 AM

PHPとPythonには独自の利点と短所があり、選択はプロジェクトの要件に依存します。 1)PHPは、Web開発に適しており、学習しやすく、豊富なコミュニティリソースですが、構文は十分に近代的ではなく、パフォーマンスとセキュリティに注意を払う必要があります。 2)Pythonは、簡潔な構文と学習が簡単なデータサイエンスと機械学習に適していますが、実行速度とメモリ管理にはボトルネックがあります。

PHPの目的:動的なWebサイトの構築PHPの目的:動的なWebサイトの構築Apr 15, 2025 am 12:18 AM

PHPは動的なWebサイトを構築するために使用され、そのコア関数には次のものが含まれます。1。データベースに接続することにより、動的コンテンツを生成し、リアルタイムでWebページを生成します。 2。ユーザーのインタラクションを処理し、提出をフォームし、入力を確認し、操作に応答します。 3.セッションとユーザー認証を管理して、パーソナライズされたエクスペリエンスを提供します。 4.パフォーマンスを最適化し、ベストプラクティスに従って、ウェブサイトの効率とセキュリティを改善します。

PHP:データベースとサーバー側のロジックの処理PHP:データベースとサーバー側のロジックの処理Apr 15, 2025 am 12:15 AM

PHPはMySQLIおよびPDO拡張機能を使用して、データベース操作とサーバー側のロジック処理で対話し、セッション管理などの関数を介してサーバー側のロジックを処理します。 1)MySQLIまたはPDOを使用してデータベースに接続し、SQLクエリを実行します。 2)セッション管理およびその他の機能を通じて、HTTPリクエストとユーザーステータスを処理します。 3)トランザクションを使用して、データベース操作の原子性を確保します。 4)SQLインジェクションを防ぎ、例外処理とデバッグの閉鎖接続を使用します。 5)インデックスとキャッシュを通じてパフォーマンスを最適化し、読みやすいコードを書き、エラー処理を実行します。

PHPでのSQL注入をどのように防止しますか? (準備された声明、PDO)PHPでのSQL注入をどのように防止しますか? (準備された声明、PDO)Apr 15, 2025 am 12:15 AM

PHPで前処理ステートメントとPDOを使用すると、SQL注入攻撃を効果的に防ぐことができます。 1)PDOを使用してデータベースに接続し、エラーモードを設定します。 2)準備方法を使用して前処理ステートメントを作成し、プレースホルダーを使用してデータを渡し、メソッドを実行します。 3)結果のクエリを処理し、コードのセキュリティとパフォーマンスを確保します。

See all articles

ホット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ヘンタイを無料で生成します。

ホットツール

mPDF

mPDF

mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。

AtomエディタMac版ダウンロード

AtomエディタMac版ダウンロード

最も人気のあるオープンソースエディター

EditPlus 中国語クラック版

EditPlus 中国語クラック版

サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

PhpStorm Mac バージョン

PhpStorm Mac バージョン

最新(2018.2.1)のプロフェッショナル向けPHP統合開発ツール

WebStorm Mac版

WebStorm Mac版

便利なJavaScript開発ツール