The C language contains a parameter with an uncertain length, for example, "...". It is mainly used in functions with an uncertain number of parameters. The most common example is the printf function.
The C language uses macros such as va_start to process these variable parameters. In fact, the principle is quite simple, that is, according to the characteristics of the parameter stack, starting from the fixed parameter closest to the first variable parameter, get the address of each variable parameter in turn.
Standard C LanguageHeader file <stdarg. h>Specifically used to deal with the variable parameter list, which contains a set of macros and a va_list typedef declaration. Different platforms have different definitions. The macro definition in x86 is as follows:
typedef char * va_list;#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )#define va_end(ap) ( ap = (va_list)0 )
_ Intsizeof (n) macro is used to consider the systems with memory addresses to be aligned (it can be seen that the compiler must meet certain alignment rules when generating parameter calls ).
To obtain each variable parameter from a fixed parameter in sequence, va_start and va_arg make full use of the following two points:
1. When using C language for function calling, first press the last parameter to the stack (C language functions are pushed to the stack from the right to the left)
2. The memory allocation sequence on the X86 platform is from high-address memory to low-address memory.
<High address>
Nth Variable Parameter
Variable parameters for the N-1
......
2nd variable parameters
1st Variable Parameter AP
Fixed parameter V
<Low-end address>
- Obviously, & V is the address of a fixed parameter in the memory. After va_start is called, the AP points to the first variable parameter. This macro is used to increase the memory size occupied by V on the memory address of V, so that the address of the first variable parameter is obtained.
- Next, we can imagine that if I can determine the type of the variable parameter, then I will know how much memory it occupies and I will be able to get the address of the next variable parameter.
- The purpose of va_arg is to return the value pointed to by the current AP and move the AP back. It first points the AP to the next variable parameter, and then subtract the size of the current variable parameter to get the memory address of the current variable parameter, perform another type conversion and return its value.
- To determine the type of each variable parameter, either the default type or the fixed parameter contains information so that the program can determine the type of each variable parameter (such as printf, analyze the format string to determine the type of each variable parameter)
- The va_end macro no longer directs the AP to a valid memory address.
The general usage principle is:
(1) first define a va_list variable in the function. Here is arg_ptr, which is a pointer to the parameter.
(2) then use the va_start macro to initialize the variable arg_ptr. The second parameter of this macro is the first parameter of the first variable parameter, which is a fixed parameter.
(3) then return the variable parameter value with va_arg and assign the value to J. The second parameter of va_arg is the type of the parameter to be returned. Here it is int type.
(4) use the va_end macro to end variable parameter acquisition. (If the function has multiple variable parameters, call va_arg to obtain each parameter in sequence)
(5) These three macros are only used to determine the memory address of each parameter in the Variable Parameter List. The Compiler does not know the actual number of parameters. The programmer must determine the number of parameters, as shown in figure
A) This method is used to set the flag-printf function in a fixed parameter.
B) set a special end mark in advance, that is, to input a variable parameter, and set the value of the last variable parameter to this special value during the call, in the function body, determine whether the parameter end is reached based on this value.
(6) The key to variable parameters is to find a way to get the address of each parameter. The method to get the address is determined by the following factors:
A) function stack growth direction
B) Input stack order of parameters
C) CPU alignment
D) memory address expression
(7) after obtaining the address and combining the parameter type, the programmer can correctly process the parameter.
//Example: void simple_va_fun(int i, ...) { va_list arg_ptr; int j=0; va_start(arg_ptr, i); j=va_arg(arg_ptr, int); va_end(arg_ptr); printf("%d %d\n", i, j); return; }
//-----------------------------------------------------------------------------
[Problem]: Is there a way to write a function. The specific form of this function parameter can be determined at runtime?
Currently, there is no "regular" solution, but there is a single eccentric, because there is a function that has set an example for us in this regard, that is, main (), its prototype is:
Int main (INT argc, char * argv []);
Think deeply, "You can only determine the parameter format at runtime", that is, you cannot see the accepted parameters from the Declaration, that is, the parameters do not have a fixed form at all.
A common method is to define a void * type parameter, use it to point to the actual parameter area, and then explain the meaning of the parameters as needed in the function.
This is the meaning of argv in the main function, and argc is used to indicate the actual number of parameters, which provides further convenience for our use. Of course, this parameter is not required.
Although the parameters do not have a fixed form, we must parse the meaning of the parameters in the function. Therefore, we naturally have a requirement that the parameter zone must be set between the caller and the called.
The content format, size, validity, and other aspects have reached an agreement, otherwise it will be miserable to say different things.
[Problem]: I want to use va_arg to extract the parameter with the function pointer type in variable length parameters, but the result is always incorrect. Why?
This is related to the implementation of va_arg. A simple and demo version of va_arg is implemented as follows:
# Define va_arg (argp, type) (* (type *) (argp) + = sizeof (type)-sizeof (type )))
The argp type is char *. If you want to extract parameters of the function pointer type from the variable parameter list using va_arg, such as int (*)(),
Then va_arg (argp, INT (*) () is extended:
(* (INT (*) () *) (argp) + = sizeof (INT (*) ()-sizeof (INT (*)())))
Obviously, (INT (*) () *) is meaningless.
To solve this problem, define the function pointer as an independent data type using typedef, for example:
Typedef int (* funcptr )();
At this time, calling va_arg (argp, funcptr) will be extended:
(* (Funcptr *) (argp) + = sizeof (funcptr)-sizeof (funcptr )))
In this way, you can compile it.
[Problem]: There is such a function with variable length parameters. The following code is used to obtain real parameters of the float type:
Va_arg (argp, float );
Can this be done?
No. In variable length parameters, the "widening" principle is applied. That is, the float type is extended to double; char and short are extended to int.
Therefore, if you want to obtain a parameter of the float type in the variable length parameter list, you need to use va_arg (argp, double ). Va_arg (argp, INT) is used for char and short types ).