1. What are variable parameters?
In C language programming, we sometimes encounter some functions with variable parameter numbers, such as the printf () function. Its prototype is:
Int printf (const char * format ,...);
In addition to the fixed format parameter, the number and type of parameters that follow are variable (with three vertices "…" Parameters placeholder). The actual call can take the following form: printf ("% d", I );
Printf ("% s", S );
Printf ("the number is % d, string is: % s", I, S );
These are already familiar to everyone. However, the question of how to write variable parameters to C functions and how to implement these Variable Parameter Function compilers has been bothering me for a long time. This article will discuss this issue and hope to help you.
2. Write a simple variable parameter C function
First look at the example program. This function must have at least one integer parameter, followed by a placeholder ..., In this example, all input parameters must be integers. The function only prints the values of all parameters.
The function code is as follows:
// Example code 1: use of variable parameter functions
# Include "stdio. H "# include" stdarg. H "Void simple_va_fun (INT start ,...) {va_list arg_ptr; int nargvalue = start; int nargcout = 0; // number of variable parameters va_start (arg_ptr, start ); // determine the memory start address of the Variable Parameter Based on the fixed parameter address. Do {++ nargcout; printf ("the % d th Arg: % d \ n", nargcout, nargvalue); // output the values of each parameter nargvalue = va_arg (arg_ptr, INT); // get the value of the next variable parameter} while (nargvalue! =-1); return;} int main (INT argc, char * argv []) {simple_va_fun (100,-1); simple_va_fun (100,200,-1 ); return 0 ;}
The following code is explained:
From the implementation of this function, we can see that the following steps should be taken to use variable parameters:
(1) The following macros will be used in the program:
void va_start( va_list arg_ptr, prev_param );type va_arg( va_list arg_ptr, type );void va_end( va_list arg_ptr );
Va here is the meaning of Variable-argument (Variable Parameter.
These macros are defined in stdarg. H, so programs that use variable parameters should include this header file.
(2) The function first defines a va_list variable, which is arg_ptr.
Volume is the pointer that stores the parameter address. The parameter value can be obtained only after the parameter address is obtained, combined with the parameter type.
(3) Use the va_start macro to initialize the variable arg_ptr defined in (2). The second parameter of this macro is the first parameter in the variable parameter list, that is, the last fixed parameter.
(4) use the va_arg macro in sequence to make arg_ptr return the address of the Variable Parameter. After this address is obtained, the parameter value can be obtained based on the parameter type.
The condition indicates whether the parameter value is-1. Note that the called function does not know the correct number of variable parameters when calling. The programmer must specify the end condition in the code. As to why it does not know the number of parameters, the reader will naturally understand after reading the internal implementation mechanisms of these macros.
(2) Processing of variable parameters in the Compiler
We know that va_start, va_arg, and va_end are in stdarg. H is defined as a macro. Because 1) the hardware platform is different, 2) the compiler is different, so the defined macro is also different. Let's take a look at stdarg in VC ++ 6.0. code in H (the file path is \ vc98 \ include \ stdarg under the VC installation directory. h)
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 )
The following describes the meaning of the Code:
1. First, we define va_list as char *, because on our current PC, the character pointer type can be used to store memory unit addresses. On some machines, va_list is defined as void *.
2. Define _ intsizeof (n) mainly for some systems that require memory alignment. This macro aims to get the actual memory size of the last fixed parameter. The sizeof operator is directly used on my machine, which has no impact on the running structure of the program. (I will see my own implementations later ).
3. va_start is defined as & V + _ intsizeof (V). Here & V is the starting address of the last fixed parameter, plus the actual occupied size, the starting memory address of the first variable parameter is obtained. So after we run va_start (AP, V), the AP points to the memory address of the first variable parameter. With this address, it will be easy in the future.
Here you need to know two things:
(1) On intel + Windows machines, the function stack is oriented down. The memory address of the stack top pointer is lower than the stack bottom pointer, therefore, the data of the advanced stack is stored in the high address of the memory.
(2) among the vast majority of C compilers such as Vc, by default, the parameter stack-to-stack order is from right to left. Therefore, the memory model after the parameter stack is shown in: the address of the last fixed parameter is located under the first variable parameter and is continuously stored.
| ------------------------ | Last variable parameter |-> high memory address | -------------------------- | ................... | ------------------------ | nth variable parameter |-> the place indicated by arg_ptr after va_arg (arg_ptr, INT) | the address of nth variable parameter. | --------------- | ............................... | -------------------------- | First variable parameter |-> va_start (arg_ptr, start) next arg_ptr refers to | the address of the first variable parameter | ----------------- | ------------------------ -- | the last fixed parameter |-> Start address | -----------------|. ................ | -------------------------- | --------------- |-> low memory address
(4) va_arg (): With the good foundation of va_start, we have obtained the address of the first variable parameter, in va_arg () the task in is to obtain the value of this parameter based on the specified parameter type, and adjust the pointer to the starting address of the next parameter.
Therefore, now let's look at the implementation of va_arg (). We should be aware of it:
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
This macro has done two things,
① Use the type name entered by the user to forcibly convert the parameter address to obtain the value required by the user
② Calculate the actual size of this parameter, and adjust the pointer to the end of this parameter, that is, the first address of the next parameter, for later processing.
(5) interpretation of the va_end macro: The X86 platform is defined as AP = (char *) 0, so that the AP no longer points to the stack, but is the same as null. some are directly defined as (void *) 0, so that the compiler will not generate code for va_end. For example, GCC is defined in this way on the Linux X86 platform. you should pay attention to one problem: Because the address of the parameter is used in the va_start macro, the parameter cannot be declared as a register variable or as a function or array type. this is the description of va_start, va_arg, and va_end. We should note that different operating systems and hardware platforms have different definitions, but their principles are similar.
(3) Notes for variable parameters in programming
Because va_start, va_arg, and va_end are defined as macros, it seems stupid. the types and numbers of variable parameters are completely controlled by the program code in this function, it cannot intelligently identify the number and type of different parameters. someone may ask: Isn't Intelligent Recognition parameters implemented in printf? That is because the function printf analyzes the parameter type from the fixed parameter format string, and then calls va_arg to obtain variable parameters. that is to say, if you want to implement Intelligent Identification of variable parameters, you must make judgments in your own programs. for example, a possible implementation of printf is provided in section 7.3 of the C's typical textbook the C programming language.
(4) summary:
1. The three Macros in the Standard C library are used only to determine the memory address of each parameter in the Variable Parameter List. The Compiler does not know the actual number of parameters.
2. in actual application code, the programmer must determine the number of parameters, as shown in figure
(1) This method is used to set the flag-printf function in fixed parameters. An example is provided later.
(2) set a special end mark in advance, that is, to input a variable parameter. when calling the variable parameter, set the value of the last variable parameter to this special value, in the function body, determine whether the parameter end is reached based on this value. The code above this article adopts this method.
No matter which method is used, programmers should tell the caller their conventions in the document.
3. 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:
① Function stack growth direction
② Input stack order of parameters
③ CPU alignment
④ Memory address expression
Combined with the source code, we can see that the implementation of va_list is determined by ④, and the introduction of _ intsizeof (n) is determined by ③, he and ① (2) jointly determine the implementation of va_start. Finally, the existence of va_end is a reflection of a good programming style, and the pointer that is no longer used is set to null, which can prevent future misoperations.
4. After obtaining the address and combining the parameter type, the programmer can process the parameter correctly. After understanding the above points, I believe that readers with a little experience can write implementations suitable for their own machines. The following is an example.
(5) extension-implement simple variable parameter functions by yourself.
The following is a simple implementation of the printf function. For more information, see <The C programming language>.
# Include "stdio. H "# include" stdlib. H "Void myprintf (char * FMT ,...) // a simple implementation similar to printf, // The parameters must be int type {char * parg = NULL; // equivalent to the original va_list char C; parg = (char *) & FMT; // do not write P = FMT !! Because here we need to get the address of the // parameter, rather than the value parg + = sizeof (FMT); // It is equivalent to the original va_start do {c = * FMT; If (C! = '%') {Putchar (c); // output character as is} else {// output data by format character switch (* ++ FMT) {Case 'D ': printf ("% d", * (int *) parg); break; Case 'X': printf ("% # X", * (int *) parg); break; default: break;} parg + = sizeof (INT); // equivalent to the original va_arg }++ FMT;} while (* FMT! = '\ 0'); parg = NULL; // equivalent to va_end return;} int main (INT argc, char * argv []) {int I = 1234; int J = 5678; myprintf ("the first test: I = % d \ n", I, j); myprintf ("The secend test: I = % d; % x; j = % d; \ n ", I, 0 xabcd, J); System (" pause "); Return 0 ;}
The execution results on Intel + Win2k + vc6 are as follows:
The first test: 1 = 1234
The secend test: I = 1234; 0 xabcd; j = 5678;