In-depth analysis of parameter passing principles in PHP

Source: Internet
Author: User
In-depth analysis of parameter passing principles in PHP

When writing php extensions, it seems that the parameter (that is, the variable passed to zend_parse_parameters) does not need to be 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. // Free (str) is not required)
  10. }

Run properly: test ("Hello World"); // print Hello World. you don't have to worry about memory leakage in the test function. php will automatically help us reclaim the variables used to save parameters.

How exactly does php do it? To explain this problem, we still need to see how php transmits parameters.

In simple terms, the EG in php stores a stack specifically used to store parameters, called argument_stack. Whenever a function call occurs, php presses the input parameters into EG (argument_stack ). Once the function call ends, EG (argument_stack) is cleared and waits for the next function call.

There are some differences between the implementation of php5.2 and 5.3 on the structure and purpose of the struct of EG (argument_stack. This article mainly takes 5.2 as an example. the changes of 5.3 + will be discussed later.

It is about argument_stack in 5.2, which looks simple and clear. The stack top and bottom are fixed to NULL. Parameters received by the function are pushed to the stack sequentially from left to right. Note that an additional long value will be pushed into the stack, indicating the number of parameters in the stack (10 in total ).

So what is the parameter pushed into argument_stack? Actually, they are zval type pointers. The zva they point to may be a CV variable, an is_ref = 1 variable, a constant number, or a constant string.

In php5.2, EG (argument_stack) is implemented as zend_ptr_stack:

  1. Typedef struct _ zend_ptr_stack {
  2. Int top; // number of current elements in the stack
  3. Int max; // The maximum number of elements in the stack
  4. Void ** elements; // stack bottom
  5. Void ** top_element; // stack top
  6. } Zend_ptr_stack;

Initializing argument_stack initializes argument_stack before php processes specific requests. more accurately, it is in the startup process of the php interpreter.

In the init_executor function, we find the following two rows:

  1. Zend_ptr_stack_init (& EG (argument_stack ));
  2. Zend_ptr_stack_push (& EG (argument_stack), (void *) NULL );

These two lines represent the initialization of EG (argument_stack), followed by a NULL. Since EG is a global variable, all data in EG (argument_stack) is 0 before zend_ptr_stack_init is called.

Zend_ptr_stack_init is easy to implement.

  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; // The stack size is initialized to 64
  5. Stack-> top = 0; // The number of current elements is 0
  6. }

Once argument_stack is initialized, it is immediately pushed into NULL. I don't need to go into it here. this NULL actually has no meaning.

After NULL is added to the stack, the actual memory distribution of the entire argument_stack is as follows:

After a parameter is pushed to the stack to the first NULL value, once a parameter is added to the stack, argument_stack performs the following actions: 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 ");

When the above code calls foo, a string constant is input. Therefore, what is actually pushed into the stack is a zval pointing to the storage of "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. 15 3> RETURN OP1 [IS_CONST (0) 1]

What the SEND_VAL command actually does is to 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. // In the stack, valptr points to the zval that stores hello world

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

The argument_stack after the stack entry is complete is:

As mentioned above, the number of parameters does not actually end up putting all the parameters into the stack. Php will also press an additional number to indicate the number of parameters, which does not happen in the SEND_XXX command. In fact, php will import the number of parameters into the stack before the function is actually executed.

Continue with the above example. the DO_FCALL command is used to call the foo function. Before calling foo, php will automatically fill in the last part of argument_stack.

  1. Static int zend_do_fcall_common_helper_SPEC (ZEND_OPCODE_HANDLER_ARGS)
  2. {
  3. ......
  4. // Input two values in argument_stack
  5. // The number of parameters (opline-> extended_value)
  6. // One is 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. // Clear argument_stack
  22. Zend_ptr_stack_clear_multiple (TSRMLS_C );
  23. ......
  24. ZEND_VM_NEXT_OPCODE ();
  25. }

After the number of input parameters and NULL, the entire argument_stack for foo call has been completed. Obtain the parameters and continue with the above example. Go deep into the foo function and see what the opcode of foo 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]

The first command is RECV, which is literally used to obtain parameters in the stack. Actually, SEND_VAL and RECV are similar. Before each function call, SEND_VAL performs RECV within the function. The RECV command is not required. A recv is generated only when a user-defined function is called. The extended functions we have compiled, built-in php functions, do not have RECV.

Note that each SEND_VAL and RECV can only process one parameter. That is to say, if there are multiple parameters in the parameter passing process, several SEND_VAL and several RECV will be generated. Here is an interesting topic. what is the order of input parameters and parameters?

The answer is that SEND_VAL will press the stack from left to right, and get the parameters from left to right in the same way as RECV.

  1. Static int ZEND_RECV_SPEC_HANDLER (ZEND_OPCODE_HANDLER_ARGS)

  2. {
  3. ...... // Param takes the parameter order along the top of the stack --> the 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. // Verify 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. // Obtain 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. }

Both zend_assign_to_variable_reference and zend_receive complete "GET parameters ". "Getting parameters" is not easy to understand. What exactly does it actually do?

In the end, it is easy to add the parameter to the symbol table during the current function execution, which corresponds to EG (current_execute_data)-> symbol_table. In this example, after RECV is completed, the symbol_table in the function contains a symbol 'str', whose value is "hello world ".

However, argument_stack does not have any changes, because RECV only reads parameters and does not generate pop-like operations on stacks.

Cleanup of print_r inside argument_stackfoo is also a function call, which also produces a stack-> cleanup operation. Therefore, the argument_stack before print_r execution is:

After print_r is executed, argument_stack returns to foo's RECV state.

The process of calling print_r is not the focus of this article. We are concerned about how php clears argument_stack after calling foo.

The do_fcall code snippet shown above shows that the cleanup is completed 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. // Number of parameters stored on the top of the stack
  5. Int delete_count = (int) (zend_uintptr_t) * p;
  6. EG (argument_stack). top-= (delete_count + 2 );
  7. // Clean up from top to bottom
  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. }

Note that zval pointer in the stack is cleared here, and zval_ptr_dtor is used. Zval_ptr_dtor will reduce the refcount by 1. Once the refcount is reduced to 0, the memory area where the variable is saved will be actually recycled.

In this example, after foo is called, the zval status of "hello world" is saved as follows:

  1. Value "hello world"
  2. Refcount 1
  3. Type 6
  4. Is_ref 0

Since refcount has only 1 left, zval_ptr_dtor will actually destroy "hello world" from the memory.

The argument_stack memory status after Stack cancellation is:

It can be seen that the argument_stack in is the same as that after initialization. In this case, argument_stack is ready for calling the callback function.

Back to the question at the beginning of the article... why don't I need free (str? After you understand argument_stack, you can understand this problem.

Because str points to the memory address that actually stores "hello world" in zval. Suppose the extended functions are 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. }

Call

  1. $ A = "hello world ";
  2. Test ($ );
  3. Echo $;

"Hello world" is output ". Although we call test instead of passing the reference of $ a, the actual effect is equivalent to test (& $ ).

In short, there is only one $ a in the memory, whether in the CV array or in argument_stack. Zend_parse_parameters does not copy a copy of data for function execution. In fact, it cannot. Therefore, after the function is completed, if $ a is used in no other place, php will help us free up argument_stack. If other code is still in use, you cannot manually free it; otherwise, the memory area of $ a will be damaged.

Note that php automatically recycles not every variable used in the write extension function. So do not be soft when you are 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.