Author: laomai (original, please note the forums and C/C ++ e-magazines from csdn when reprinting)
Source: http://topic.csdn.net/t/20041124/09/3582660.html
Preface:
This article is adapted from the article "Variable Parameter usage in C Language" by kevintz, a netizen. before writing this article, I would like to express my sincere respect and thanks to this predecessor.
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 "…" Parameter placeholder), the actual call can be in 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 has at least one integer parameter, followed by a placeholder ..., In this example, all input parameters must be integers. The function is to print 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 );
// Obtain 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;
}
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. Here it is arg_ptr, which is a pointer to the parameter address. the parameter value can be obtained only after obtaining the parameter address and combining 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 previous 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. Then output.
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 the following macros.
Iii. Processing of variable parameters in the Compiler
We know that va_start, va_arg, and va_end are defined as Macros in stdarg. h,
Because 1) different hardware platforms
2) the compiler is different, so the defined macros are different. Let's take a look at the code in stdarg. h in VC ++ 6.0 (the file path is/vc98 in the installation directory of Vc ).
/Include/stdarg. 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. The definition of va_start is & V + _ intsizeof (v), and & V is the starting address of the last fixed parameter. After adding the size of it, we get the first one.
The starting memory address of the variable parameter. So we run va_start (AP,
V) Later, 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) In most C compilers such as Vc, the parameter stack-to-stack order is from right to left. Therefore,
As shown in the memory model after the parameter is added to the stack, the address of the last fixed parameter is located under the first variable parameter and is continuously stored.
| -- ------------- |
| Last fixed parameter |-> high memory address
|--------------- |
........................
| ------------------------------- |
| Nth variable parameter |-> place indicated by arg_ptr after va_arg (arg_ptr, datatype)
| ------------------------------- |
...................
| --- ------------ |
| First variable parameter |-> place indicated by arg_ptr after va_start (arg_ptr, start)
| Address of the first Variable Parameter
| --------------- |
| ------------- -- |
|
| 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 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 is no longer
Point to the stack, but like null. Some are directly defined as (void *) 0), so the compiler does not
Code is generated for va_end. For example, GCC is defined in this way on the x86 Platform of Linux.
You should pay attention to one problem: Because the address of the parameter is used in the va_start macro, all parameters cannot be declared as register variables or as functions or arrays.
This is the description of va_start, va_arg, and va_end.
Different operating systems and hardware platforms have different definitions, but their principles are similar.
Iv. Notes for variable parameters in programming
Because va_start, va_arg, and va_end are defined as macros, it seems stupid,
The type and number 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's because the function
Printf analyzes the parameter type from a fixed 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
In section 7.3 of programming, a possible implementation method of printf is provided, which is not described here due to space reasons.
V. 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 uses this method-when the variable parameter value is-1, it is deemed that the end of the parameter list is obtained.
No matter which method is used, programmers should tell the caller their conventions in the document. This is inconvenient.
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 good embodiment of programming style-Setting the pointer that is no longer used to null 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 experienced readers can write implementations suitable for their own machines. The following is an example.
6. Practice-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 of the int type.
{
Char * parg = NULL; // equivalent to the original va_list
Char C;
Parg = & FMT; // do not write P = FMT !! Because here we need to take the address of the parameter, not the value
Parg + = sizeof (FMT); // equivalent to the original va_start
Do
{
C = * FMT;
If (C! = '% ')
{
Putchar (c); // output character as is
}
Else
{
// Output data by formatted characters
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;