Home Backend Development PHP Tutorial In-depth analysis of PHP parameter passing principle

In-depth analysis of PHP parameter passing principle

Jul 25, 2016 am 09:13 AM

When writing PHP extensions, it seems that parameters (i.e., variables passed to zend_parse_parameters) do 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. // No need for free(str)
  10. }
Copy code

operating normally: test("Hello World"); // Print Hello World There is no need to worry about memory leaks in the test function here. PHP will automatically help us recycle these variables used to save parameters.

So how does php do it? To explain this problem, we still have to look at how PHP passes parameters.

EG (argument_stack) introduction To put it simply, a stack specifically used to store parameters is saved in EG in PHP, named argument_stack. Whenever a function call occurs, php will push the incoming parameters into EG (argument_stack). Once the function call ends, the EG (argument_stack) is cleared and waits for the next function call.

Regarding the struct structure and purpose of EG (argument_stack), there are some differences between the implementation of php5.2 and 5.3. This article mainly takes 5.2 as an example, and we will discuss the changes in 5.3+ later. In-depth analysis of PHP parameter passing principle

The above picture is a rough diagram of argument_stack in 5.2, which looks simple and clear. Among them, the top and bottom of the stack are fixed to NULL. The parameters received by the function are pushed onto the stack in order from left to right. Note that an additional long value will be pushed in at the end, indicating the number of parameters in the stack (10 in the picture above).

What are the parameters that are pushed into argument_stack? In fact, they are pointers of zval type. The zva they point to may be a CV variable, a variable with is_ref=1, or a constant number or constant string.

EG (argument_stack) is specifically implemented as 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 the stack
  5. void * *top_element; // Top of stack
  6. } zend_ptr_stack;
Copy code

Initialize argument_stack The work of initializing argument_stack occurs before PHP processes a specific request. To be more precise, it is during the startup process of the PHP interpreter.

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

These two lines represent, respectively, initialization EG(ar gument_stack), Then push a NULL. Since EG is a global variable, all data in EG (argument_stack) are all 0 before zend_ptr_stack_init is actually called.

zend_ptr_stack_init implementation is very 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-& gt; max = PTR_STACK_BLOCK_SIZE; // The stack size is initialized to 64
  5. stack->top = 0; // The current number of elements is 0
  6. }
Copy code

Once argument_stack is initialized, it will NULL is pushed. There is no need to go into detail here, this NULL actually has no meaning.

After NULL is pushed into the stack, the actual memory distribution of the entire argument_stack is as follows: In-depth analysis of PHP parameter passing principle

Push parameters onto stack After pushing the first NULL, once another parameter is pushed onto the stack, the following actions will occur in argument_stack: 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 above code passes in a string constant when calling foo. Therefore, what is actually pushed onto the stack is a zval pointing to the storage "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 ]
Copy What the code

SEND_VAL instruction actually does is push "hello world" into argument_stack.

  1. int ZEND_SEND_VAL_SPEC_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

  2. {
  3. zval *valptr, *value;
  4. value = &opline- >op1.u.constant ;

  5. ALLOC_ZVAL(valptr);
  6. INIT_PZVAL_COPY(valptr, value);
  7. if (!0) {
  8. zval_copy_ctor(valptr);
  9. }
  10. // Push to the stack, valptr points to store hello world zval

  11. zend_ptr_stack_push(&EG(argument_stack), valptr);
  12. ……
  13. }
Copy the code

The argument_stack after the push is completed is:

In-depth analysis of PHP parameter passing principle3

Number of parameters As mentioned earlier, it is not actually just pushing all the parameters onto the stack. PHP will also push an additional number to represent the number of parameters. This work does not occur during the SEND_XXX command. In fact, before actually executing the function, PHP will push the number of parameters onto the stack.

Continuing to use the above example, the DO_FCALL instruction is used to call the foo function. Before calling foo, php will automatically fill in the last block of argument_stack.

  1. static int zend_do_fcall_common_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS)
  2. {
  3. ……
  4. // Push 2 values ​​into argument_stack
  5. // One is the number of parameters (i.e. opline->extended_value)
  6. // One is the identifier NULL on the top of the stack
  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. // Call foo function
  14. zend_execute(EG(active_op_array) TSRMLS_CC);
  15. }
  16. else { /* ZEND_OVERLOADED_FUNCTION +/ used for The entire argument_stack of the foo call has been completed.
  17. Get parameters Continuing with the example above. Dive into the foo function and see what foo's opcode looks like.
  18. line # * op fetch ext return operands
  19. ---------------------------------- --------------------------------------------------
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 ]

In-depth analysis of PHP parameter passing principle

Copy code

The first instruction is RECV, which is literally used to obtain the parameters in the stack. In fact, SEND_VAL and RECV have a somewhat corresponding feeling. SEND_VAL before each function call, RECV is performed inside the function. Why not say it is completely corresponding? In fact, the RECV instruction is not necessarily required. RECV is generated only when a user-defined function is called. The extension functions we write and the built-in functions that come with PHP will not have RECV.

It should be pointed out 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. This leads to a very interesting topic, what is the order of passing in parameters and getting parameters?

The answer is that SEND_VAL will push the parameters onto the stack from left to right, while RECV will get the parameters from left to right.

  1. static int ZEND_RECV_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS)

  2. {
  3. ……//param takes the parameters in order along the top of the stack-->bottom of the stack
  4. if (zend_ptr_stack_get_arg(arg_num, (void * *) ¶m 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. // Get parameters
  13. if (PZVAL_IS_REF(*param)) {
  14. zend_as sign_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 The code

zend_assign_to_variable_reference and zend_receive will complete "getting parameters". "Getting parameters" is not easy to understand. What does it actually do?

In the final analysis, it is very simple. "Getting parameters" is to add this parameter to the "symbol table" during the execution of the current function, which specifically corresponds to EG (current_execute_data)->symbol_table. In this example, after RECV is completed, there is a symbol 'str' in the symbol_table in the function body, and its value is "hello world".

But argument_stack has not changed at all, because RECV only reads parameters and does not perform a pop operation on the stack.

In-depth analysis of PHP parameter passing principle5

Clean argument_stack The print_r inside foo is also a function call, so the stack push-->stack clear operation will also occur. Therefore the argument_stack before print_r is executed is: In-depth analysis of PHP parameter passing principle6

After print_r is executed, argument_stack returns to the state where foo just finished RECV.

The specific process of calling print_r is not the focus of this article. What we care about is how PHP cleans up argument_stack after calling foo.

As can be seen from the do_fcall code snippet shown above, the cleaning work 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. // Get the number of parameters saved 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. }
Copy code

Pay attention to clearing the zval pointer in the stack here, zval_ptr_dtor is used. zval_ptr_dtor will decrement the refcount by 1. Once the refcount is reduced to 0, the memory area storing the variable will be truly recycled.

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

  1. value "hello world"
  2. refcount 1
  3. type 6
  4. is_ref 0
Copy code

Since refcount is only 1, zval_ptr_dtor will actually destroy "hello world" from memory.

The memory status of argument_stack after stack elimination is:

In-depth analysis of PHP parameter passing principle7

It can be seen that the argument_stack in the above picture is the same as it was just initialized. At this point argument_stack is truly ready for the next function call.

Back to the question at the beginning of the article... Why no need for free(str)? After understanding argument_stack, you can easily understand this problem.

Because str points to the memory address where "hello world" is actually stored in zval. 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 the code

then call

  1. $a = "hello world";
  2. test($a);
  3. echo $a;
Copying the code

will output "Hello world". Although when we call test, we do not pass the reference of $a, but the actual effect is equivalent to test(&$a).

Simply put, there is only one copy of $a in memory, whether it is in the CV array or argument_stack. zend_parse_parameters does not copy a copy of the data for function execution, and in fact it cannot do so. Therefore, when the function is completed, if $a is not used elsewhere, PHP will help us free it when cleaning up argument_stack. If it is still used by other code, it cannot be freed manually, otherwise the memory area of ​​$a will be destroyed.

Note that not every variable used in writing extension functions will be automatically recycled by PHP. So when it’s time to be free, don’t be soft:)



Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn

Hot AI Tools

Undresser.AI Undress

Undresser.AI Undress

AI-powered app for creating realistic nude photos

AI Clothes Remover

AI Clothes Remover

Online AI tool for removing clothes from photos.

Undress AI Tool

Undress AI Tool

Undress images for free

Clothoff.io

Clothoff.io

AI clothes remover

AI Hentai Generator

AI Hentai Generator

Generate AI Hentai for free.

Hot Article

R.E.P.O. Energy Crystals Explained and What They Do (Yellow Crystal)
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. Best Graphic Settings
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. How to Fix Audio if You Can't Hear Anyone
4 weeks ago By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25: How To Unlock Everything In MyRise
1 months ago By 尊渡假赌尊渡假赌尊渡假赌

Hot Tools

Notepad++7.3.1

Notepad++7.3.1

Easy-to-use and free code editor

SublimeText3 Chinese version

SublimeText3 Chinese version

Chinese version, very easy to use

Zend Studio 13.0.1

Zend Studio 13.0.1

Powerful PHP integrated development environment

Dreamweaver CS6

Dreamweaver CS6

Visual web development tools

SublimeText3 Mac version

SublimeText3 Mac version

God-level code editing software (SublimeText3)

Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Explain JSON Web Tokens (JWT) and their use case in PHP APIs. Apr 05, 2025 am 12:04 AM

JWT is an open standard based on JSON, used to securely transmit information between parties, mainly for identity authentication and information exchange. 1. JWT consists of three parts: Header, Payload and Signature. 2. The working principle of JWT includes three steps: generating JWT, verifying JWT and parsing Payload. 3. When using JWT for authentication in PHP, JWT can be generated and verified, and user role and permission information can be included in advanced usage. 4. Common errors include signature verification failure, token expiration, and payload oversized. Debugging skills include using debugging tools and logging. 5. Performance optimization and best practices include using appropriate signature algorithms, setting validity periods reasonably,

Explain the concept of late static binding in PHP. Explain the concept of late static binding in PHP. Mar 21, 2025 pm 01:33 PM

Article discusses late static binding (LSB) in PHP, introduced in PHP 5.3, allowing runtime resolution of static method calls for more flexible inheritance.Main issue: LSB vs. traditional polymorphism; LSB's practical applications and potential perfo

Framework Security Features: Protecting against vulnerabilities. Framework Security Features: Protecting against vulnerabilities. Mar 28, 2025 pm 05:11 PM

Article discusses essential security features in frameworks to protect against vulnerabilities, including input validation, authentication, and regular updates.

Customizing/Extending Frameworks: How to add custom functionality. Customizing/Extending Frameworks: How to add custom functionality. Mar 28, 2025 pm 05:12 PM

The article discusses adding custom functionality to frameworks, focusing on understanding architecture, identifying extension points, and best practices for integration and debugging.

How to send a POST request containing JSON data using PHP's cURL library? How to send a POST request containing JSON data using PHP's cURL library? Apr 01, 2025 pm 03:12 PM

Sending JSON data using PHP's cURL library In PHP development, it is often necessary to interact with external APIs. One of the common ways is to use cURL library to send POST�...

Describe the SOLID principles and how they apply to PHP development. Describe the SOLID principles and how they apply to PHP development. Apr 03, 2025 am 12:04 AM

The application of SOLID principle in PHP development includes: 1. Single responsibility principle (SRP): Each class is responsible for only one function. 2. Open and close principle (OCP): Changes are achieved through extension rather than modification. 3. Lisch's Substitution Principle (LSP): Subclasses can replace base classes without affecting program accuracy. 4. Interface isolation principle (ISP): Use fine-grained interfaces to avoid dependencies and unused methods. 5. Dependency inversion principle (DIP): High and low-level modules rely on abstraction and are implemented through dependency injection.

What exactly is the non-blocking feature of ReactPHP? How to handle its blocking I/O operations? What exactly is the non-blocking feature of ReactPHP? How to handle its blocking I/O operations? Apr 01, 2025 pm 03:09 PM

An official introduction to the non-blocking feature of ReactPHP in-depth interpretation of ReactPHP's non-blocking feature has aroused many developers' questions: "ReactPHPisnon-blockingbydefault...

See all articles