A thorough analysis of the principle of PHP transfer parameters

Source: Internet
Author: User

When writing a php extension, it seems that the parameters (that is, the variable passed to zend_parse_parameters) do not need free. Example:

    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. No free (str)
    10. }

Copy Code

Run normally: Test ("Hello World"); Print Hello World There's no need to worry about a memory leak in the test function, and PHP will automatically recycle these variables for saving parameters.

How exactly does PHP do it? To explain the problem, you still have to see how PHP passes the parameters.

Introduction to EG (Argument_stack) in short, a stack called argument_stack is stored in the eg in PHP, which is dedicated to storing parameters. Whenever a function call occurs, PHP presses the incoming parameter into eg (argument_stack). Once the function call is finished, eg (argument_stack) is cleaned up and waits for the next function call.

There are some differences between php5.2 and 5.3 implementations regarding the struct structure and use of eg (Argument_stack). This article mainly takes 5.2 as an example, 5.3+ the change after the time to say.

It's probably argument_stack in 5.2, it looks simple and clear. Where the top and bottom of the stack are fixed to null. The parameters received by the function are pressed into the stack sequentially, in order from left to right. Note that the end will be pressed into a long value, indicating the number of arguments in the stack (medium 10).

What exactly is the parameter that is pressed into the argument_stack? is actually a zval type of pointer. The Zva they point to can be CV variables, possibly is_ref=1 variables, and possibly a constant number, or a constant string.

EG (Argument_stack) is specifically implemented as a zend_ptr_stack type in php5.2:

    1. typedef struct _ZEND_PTR_STACK {
    2. int top; The number of current elements in the stack
    3. int Max; The maximum number of elements stored in the stack
    4. void **elements; Bottom of Stack
    5. void **top_element; Top of Stack
    6. } Zend_ptr_stack;
Copy Code

Initializing the Argument_stack initialization argument_stack is done before PHP handles specific requests, more accurately in the PHP interpreter startup process.

In the Init_executor function we found the following 2 lines:

    1. Zend_ptr_stack_init (&eg (argument_stack));
    2. Zend_ptr_stack_push (&eg (argument_stack), (void *) NULL);
Copy Code

The 2 rows represent the initialization of eg (Argument_stack), followed by a null. Since eg is a global variable, all data in eg (argument_stack) is all 0 before actually invoking Zend_ptr_stack_init.

Zend_ptr_stack_init implementation is simple.

    1. Zend_api void Zend_ptr_stack_init (Zend_ptr_stack *stack)
    2. {
    3. Stack->top_element = Stack->elements = (void * *) emalloc (sizeof (void *) *ptr_stack_block_size);
    4. Stack->max = ptr_stack_block_size; Stack size is initialized to 64
    5. stack->top = 0; The current number of elements is 0
    6. }
Copy Code

Once the argument_stack is initialized, it is immediately pressed into null. There is no need to delve into this, this null does not actually have any meaning.

After NULL is in the stack, the actual memory distribution for the entire argument_stack is as follows:

Parameter into the stack after the first null is pressed, once there are arguments in the stack, then Argument_stack will take the following action: stack->top++;* (stack->top_element++) = parameter;

Use a simple PHP code to illustrate the problem:

    1. function foo ($str) {
    2. Print_r (123);
    3. }
    4. Foo ("Hello World");
Copy Code

The preceding code passes a string constant when it calls Foo. So, actually being pushed into the stack is a pointer to the zval that stores "Hello World". Use VLD to view the compiled opcode:

    1. Line # * OP fetch ext return operands
    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. 3 > RETURN op1[is_const (0) 1]
Copy Code

What the Send_val command actually does is press "Hello World" into Argument_stack.

    1. int Zend_send_val_spec_const_handler (Zend_opcode_handler_args)

    2. {
    3. ......
    4. Zval *valptr, *value;

    5. Value = &opline->op1.u.constant;

    6. Alloc_zval (VALPTR);
    7. Init_pzval_copy (valptr, value);
    8. if (!0) {
    9. Zval_copy_ctor (VALPTR);
    10. }

    11. Into the stack, valptr points to the zval that holds Hello World

    12. Zend_ptr_stack_push (&eg (argument_stack), valptr);
    13. ......
    14. }

Copy Code

The argument_stack after the stack is completed is:

Number of arguments the previous article said, in fact, not all of the parameters into the stack is finished. PHP also presses an extra number to indicate the number of parameters, which does not occur when the send_xxx command is in place. In fact, PHP will put the number of arguments into the stack before actually executing the function.

Continuing with the example above, the do_fcall instruction is used to invoke the Foo function. Before calling Foo, PHP will automatically fill in the last piece of argument_stack.

    1. static int Zend_do_fcall_common_helper_spec (Zend_opcode_handler_args)
    2. {
    3. ......
    4. 2 values are pressed into the Argument_stack
    5. One is the number of arguments (that is, opline->extended_value)
    6. One is the null that identifies the top of the stack
    7. Zend_ptr_stack_2_push (&eg (argument_stack), (void *) (zend_uintptr_t) Opline->extended_value, NULL);
    8. ......
    9. if (EX (function_state). Function->type = = zend_internal_function) {
    10. ......
    11. }
    12. else if (EX (function_state). Function->type = = zend_user_function) {
    13. ......
    14. Call the Foo function
    15. Zend_execute (EG (Active_op_array) tsrmls_cc);
    16. }
    17. else {/* zend_overloaded_function */
    18. ......
    19. }
    20. ......
    21. Clean Argument_stack
    22. Zend_ptr_stack_clear_multiple (Tsrmls_c);
    23. ......
    24. Zend_vm_next_opcode ();
    25. }
Copy Code

After pressing the number of parameters and null, the entire argument_stack used for the Foo call has been completed. Get the parameters to follow the example above. Go deep into the Foo function and see what foo's opcode looks like.

    1. Line # * OP fetch ext return operands
    2. ---------------------------------------------------------------------------------
    3. 3 0 > RECV op1[is_const (0) 1]
    4. 4 1 Send_val op1[is_const (5) 123]
    5. 2 Do_fcall 1 op1[is_const (459027) ' Print_r '
    6. 5 3 > RETURN op1[is_const (0) NULL]
Copy Code

The first instruction is recv, which is literally understood to be used to get the parameters in the stack. In fact, Send_val and recv have a somewhat corresponding feeling. Each time a function call is Send_val, recv is performed inside the function. Why not say the exact correspondence, actually recv instruction is not necessarily necessary. Recv is generated only if the user-defined function is called. The extension function we write, PHP's own built-in function, will not have recv.

It should be noted that each send_val and recv can only handle one parameter at a time. In other words, if there are multiple parameters in the process of passing the parameter, several send_val and several recv are produced. This leads to a very interesting topic, what is the order in which parameters are passed in and obtained?

The answer is that Send_val will stack the arguments from left to right, and recv the same from left to right.

    1. static int Zend_recv_spec_handler (Zend_opcode_handler_args)

    2. {
    3. ...//param take the order of the parameters along the top----and bottom of the stack
    4. if (Zend_ptr_stack_get_arg (Arg_num, (void * *) &param tsrmls_cc) ==failure) {
    5. ......
    6. } else {
    7. Zend_free_op Free_res;
    8. Zval **var_ptr;

    9. Validation parameters

    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. Get parameters
    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. }

Copy Code

Zend_assign_to_variable_reference and Zend_receive will complete the get parameters. "Get Parameters" is not very good to understand, actually what is it to do?

In the final analysis, "Get Parameters" is the addition of this parameter to the "symbol table" during the execution of the current function, which corresponds to eg (current_execute_data)->symbol_table. In this example, after recv is complete, there is a symbol ' str ' in the symbol_table in the function body, and its value is "Hello World".

But Argument_stack does not change a bit, because recv simply reads the parameters without generating a similar pop operation on the stack.

Cleaning up the print_r inside the Argument_stackfoo is also a function call, so it also generates a stack-and-clear operation. So the argument_stack before Print_r execution is:

Print_r after the execution of Argument_stack back to Foo just recv finished state.

The process of calling Print_r specifically is not the focus of this article. What we're concerned about is how PHP cleans up argument_stack after the call to Foo ends.

As can be seen in the Do_fcall code snippet shown above, cleanup is done by 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. The number of arguments saved at the top of the stack
    5. int delete_count = (int) (zend_uintptr_t) *p;
    6. EG (argument_stack). Top-= (delete_count+2);
    7. From top to bottom, clean up in turn
    8. while (--delete_count>=0) {
    9. Zval *q = * (Zval * *) (--P);
    10. *p = NULL;
    11. Zval_ptr_dtor (&Q);
    12. }
    13. EG (argument_stack). Top_element = P;
    14. }
Copy Code

Notice here that the Zval pointer is cleared in the stack, using Zval_ptr_dtor. Zval_ptr_dtor will reduce refcount by 1, and once the refcount is reduced to 0, the memory area where the variable is saved is actually recycled.

In this example, after the Foo call is complete, the Zval state of Save "Hello World" is:

    1. Value "Hello World"
    2. RefCount 1
    3. Type 6
    4. Is_ref 0
Copy Code

Since RefCount only has 1 left, Zval_ptr_dtor will actually destroy "Hello World" from memory.

After the stack is finished, the Argument_stack memory state is:

It can be seen that the argument_stack is the same as when it was first initialized. At this point, Argument_stack is really ready to meet the next function call.

Back to the beginning of the article question ... Why not need free (str)? After figuring out the argument_stack, I understood the problem very well.

Because STR points to the memory address in Zval that actually holds "Hello World". Assume that the extension function is as follows:

    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. }

Copy Code

The call

    1. $a = "Hello World";
    2. Test ($a);
    3. echo $a;
Copy Code

Will output "Hello world". Although we call test, not a $ A reference, but the actual effect is equivalent to test (& $a).

Simply put, there is only one $ A in memory, either in the CV array or in Argument_stack. Zend_parse_parameters does not copy a piece of data for function execution, and in fact it cannot. Therefore, when the function is finished, if there is no other place to use the $a,php cleanup Argument_stack will help us free. If the other code is still in use, it is more impossible to manually free, otherwise it will destroy $ A in the memory area.

Note that not every variable used in the Write extension function will be automatically reclaimed by PHP. Therefore, do not relent when free:)

  • Contact Us

    The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

    If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.