C++ variable parameter implementation method

  • 2020-04-01 21:38:01
  • OfStack

The realization of variable parameters should solve three problems:

1. How to call a function with variable arguments
2. How to compile programs with variable parameters
3. How to hold variable parameters in a function body with variable parameters
The first problem is that you can just pass in variable arguments where you can, and of course, there are a few other things that you need to pay attention to, which we'll talk about later.

The second problem is that the compiler needs to adopt a loose checking scheme at compile time, which leads to some problems, such as programming errors.

The third is the problem that I want to concern here, take C language as an example to analyze its implementation principle first.

Printf and scanf are the most common variable-parameter functions in the C language standard library, and printf's signature is


int printf(const char* format, ...);

Among them,... For mutable arguments, now write a simple example that mimics printf.

I. a simple example:


#include <windows.h>
#include <stdio.h>
void VariableArgumentMethod(int argc, ...);
int main(){
    VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9);
    return 0;
}
void VariableArgumentMethod(int argc, ...){
    //Declares a pointer to hold a variable parameter
    va_list pArg;
    //Initialize the pArg to point to the first parameter
    va_start(pArg, argc);
    //Output parameters
  for(int i = 0; i != argc; ++i){
        //Gets the parameter pointed to by the pArg and outputs it
        printf("%d, ", va_arg(pArg, int) );
    }
    va_end(pArg);
}

Void VariableArgumentMethod (int arg c,...). Argc is a variable-argument function that outputs a variable argument for the specified number of argc arguments.
VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9); Is a call to this function, and the first argument 6 is followed by six arguments.

In the function body of the VariableArgumentMethod:

1. The va_list pArg.

Defines a pointer to hold the first variable parameter by moving the pointer through the incoming variable parameter table.

2. Va_start (pArg, arg c);

Have the pArg point to the first parameter in the variable-parameter list. Argc is an argument to position, because the variable argument starts after argc, and we'll explain why later.

3. The va_arg (pArg, int);

This sentence is placed in the body of the loop to fetch parameters from the variable-parameter table. Also, it will move the pArg to the next variable parameter (if it has reached the end, it will point to a meaningless address).

4. Va_end (pArg);

Zero for pArg, which I think is optional here, because pArg is no longer needed.

 

Thus, the body of the VariableArgumentMethod function traverses the parameters passed in the variable-parameter table, and prints them out with printf("%d, ", va_arg(pArg, int)).

Two, implementation details

First, see how the compiler handles passing arguments.

The compiler passes arguments on a stack. When arguments are passed, the compiler pushes arguments from the arguments list in right-to-left order. For VariableArgumentMethod(6, 4, 7, 3, 0, 7, 9), the order is 9, 7, 0, 3, 7, 4, 6 (note that there is no difference between mutable and immutable arguments). Since the address of the stack is from high to low, the distribution of arguments in the stack after an argument is pushed is shown in the following figure. As you can see, arguments in the stack still keep the left argument at a low address and the right argument at a high address. OK, that's all I need to know.

Lower address   High address

.

6

4

7

3

0

7

9

.

The stack

 

2. Va_list, va_start, va_arg and va_end

Va_list is a defined pointer type, va_start, va_arg, and va_end are macros defined by the C language for dealing with variable parameters, in the stdarg.h file. Because the hardware platform is different, the compiler is different, causes their definition also to be different, but the basic idea is the same. The following is the definition of a related macro.


typedef char *  va_list;
#define _ADDRESSOF(v)   ( &(v) )
#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

As you can see, two more macros _ADDRESSOF and _INTSIZEOF are introduced here.

_ADDRESSOF(v) is used to get the address of the variable, which you can tell at a glance;

_INTSIZEOF(n) is used for alignment. What is alignment? , this is because the stack structure in a 32-bit machine, each cell in the stack are of 4 bytes, this is often an int type, the length of the pass but the actual parameters may not be exactly 4 bytes, or just the multiple of 4 bytes, as if the car won't sell half of the seats to passengers, if the incoming data is not just a multiple of 4 or 4 bytes, then you need to align (filling). We need to analyze why this expression is aligned.

Va_start (ap, v), the ap is used to hold a pointer to the variable parameters, v is the last of the parameters of the variable parameter, (va_list) _ADDRESSOF (v) to obtain v address, and into the va_list type, v is the last of the parameters of the variable parameter, in this case should be 6, the low address above processing stack, _INTSIZEOF (v) access to an aligned address, there should be 4, after the two together, that points to the first variable parameter, So 4 in the figure above, when you assign this value to ap, you make ap point to the first variable. From here, you can see that it is useful to define va_list as char*, since a char is one byte long and is easy to compute with Pointers.

  Va_arg (ap, t), the ap is used to hold a pointer to the variable parameters, t is to obtain the type of parameter, the ap + = _INTSIZEOF (t) for the ap to point to the next argument, however, the need to get the current value of parameters to reduce the expression come back again so, return should be a va_list type (char *) pointer, so to transition to t * solutions for reference of the reentry after operation, get the current parameter values. (note that there is an ap will be moved to the next operation parameters and cut back, I don't feel very good, on the one hand, there is a waste of operation, there will be some impact on performance, on the other hand, I would much rather take the operation of the current value and move to the next operation separation, which allows programmers to have more control, and easy to understand.)

Va_end (ap) points the ap to an empty address.

Through the above analysis, it can be found that the variable parameters in C language are accessed from the stack in order, and the three macros used in the process are just simple packaging of the operation, which can be fully realized by their own programming. And, the type and number of parameters can not be directly determine, in this case, the first parameter to the VariableArgumentMethod is used to specify the number of parameters, and parameter type conventions for plastic, this program can run normally, say to the printf, it is able to identify the number of parameters, because it must describe the parameters in the first parameter to the format string, this is the very first question mentioned at the beginning of said to pay attention to the problem. That's why it's been criticized by a lot of people, but I think it's a good way to do it, and I'll compare that to how Java and.net are implemented.

Three, Java and.net to implement the way of variable parameters.

Java since 1.5, began to support variable parameters, its definition syntax is:


void testMethod(String ... args)

For this method, call testMethod("gly", "zxy", "ChenFei");

.net also supports variable parameters, and its syntax is:


void TestMethod(params string[] args)

For this method, call TestMethod("gly", "zxy", "ChenFei");

In Java and.net, for the implementation of the basic variable parameters is the same: the compiler at compile time, the method signature of the variable parameter as an array of the appropriate type, compile the corresponding call, according to the arguments generated an array, the parameter loading which is passed to the array, in the method body of variable parameter method, by using an array of ways to use variable parameter.

Iv. Comparison of two implementation methods

C language is implemented with the Java the.net implementation way, compared to C programmers need to do more work, moreover, does increase the chances of errors, Java,.net implementation can easily determine the type and number of parameters, the C implementation is not, but the Java,.net implementation will generate temporary array, of course, Java,.net garbage collection mechanism, however, when recycled garbage is uncertain, and it is costly, recycling is a good thing, but I don't like it, I think what is not needed should be released immediately, which is the embodiment of one aspect of perfection. There is no such problem in C. The problem of number and type of parameters can be solved by convention or specification. In Java and.net, the number of parameters is passed indirectly (the length of the array), and the type of parameters is specified in the method signature. Of course, Java.net is designed for a different purpose than C.

 


Related articles: