In C language programming, we will encounter some functions with variable parameter numbers, such as printf ():
Int printf (const char * format ,...);
In addition to the fixed format parameter, the number and type of parameters that follow are variable. For example:
Printf ("% d", I );
Printf ("% s", s );
Printf ("the number is % d, string is: % s", I, s );
How do I write C Functions with variable parameters? How can the compiler implement these variable parameter functions?
This article will discuss this issue and hope to help you. C ++ friends know that these problems do not exist in C ++ because C ++ has polymorphism. However, C ++ is a superset of C. The following technologies can also be used in C ++ programs.
(1) write a simple variable parameter C function
Next we will discuss how to write a simple variable parameter C function. The following macros should be used in the program for C functions that write variable parameters:
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.
Next we will write a simple variable parameter function, which has at least one integer parameter, and the second parameter is also an integer, which is optional. The function only prints the values of these two parameters.
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 \ n", I, j );
Return;
}
We can declare our function in our header file as follows:
Extern void simple_va_fun (int I ,...);
We can call it in the program as follows:
Simple_va_fun (100 );
Simple_va_fun (100,200 );
From the implementation of this function, we can see that the following steps should be taken to use variable parameters:
First, define a va_list variable in the function. Here is arg_ptr, which is a pointer to the parameter.
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.
Then return a variable parameter with va_arg and assign it to integer j. The second parameter of va_arg is the type of the parameter to be returned, Which is int type.
Finally, use the va_end macro to end the variable parameter acquisition. Then you can use the second parameter in the function. If a function has multiple variable parameters. Call va_arg to obtain parameters in sequence.
If we call the following three methods, they are all legal, but the results are different:
Simple_va_fun (100 ); |
Result: 100-123456789 (changed value) |
Simple_va_fun (100,200 ); |
Result: 100 200 |
Simple_va_fun (100,200,300 ); |
Result: 100 200 |
We can see that the first call has an error, the second call is correct, and the third call is in conflict with the initial design of our function despite the correct result.
The following section describes the causes of these results and how variable parameters are processed in the compiler.
(2) Processing of variable parameters in the Compiler
We know that va_start, va_arg, and va_end are defined as Macros in stdarg. h. Different hardware platforms and compilers define different macros.
The following is an excerpt from the macro definition of the x86 Platform in stdarg. h in VC ++ ('\' indicates a line break ):
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)
Definition _ INTSIZEOF (n) is mainly used for some systems that require memory alignment.
C functions are pushed from right to left into the stack. Figure (1) shows the distribution of function parameters in the stack.
We can see that va_list is defined as char *, and some platforms or operating systems are defined as void *. Let's look at the definition of va_start, which is defined as & v + _ INTSIZEOF (v ), & v is the address of the fixed parameter in the stack, so after we run va_start (ap, v), the ap points to the address of the first variable parameter in the stack. :
High address |
----------------------------- |
|
|
Function return address |
|
|
----------------------------- |
|
|
...... |
|
|
----------------------------- |
|
|
Nth parameter (first variable parameter) |
|
|
----------------------------- |
<-- After va_start, the ap points |
|
N-1 parameters (the last fixed parameter) |
|
Low address |
----------------------------- |
<-- & V |
Figure (1) |
|
Then, we use va_arg () to obtain the variable parameter value of type t. The preceding example is int type. Let's take a look at the return value of the va_arg type:
J = (* (int *) (ap + = _ INTSIZEOF (int)-_ INTSIZEOF (int )));
First, ap + = sizeof (int) is directed to the address of the next parameter. Then return the int * pointer of ap-sizeof (int), which is the address of the first variable parameter in the stack (figure 2 ). Then, use * to get the content of this address (parameter value) and assign it to j.
High address |
----------------------------- |
|
|
Function return address |
|
|
----------------------------- |
|
|
...... |
|
|
----------------------------- |
<-- After va_arg, ap points |
|
Nth parameter (first variable parameter) |
|
|
----------------------------- |
<-- After va_start, the ap points |
|
N-1 parameters (the last fixed parameter) |
|
Low address |
----------------------------- |
<-- & V |
Figure (2) |
|
The last thing to say is the meaning 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 format string, and then calls va_arg to obtain the variable parameter. That is to say, if you want to implement Intelligent Identification of variable parameters, you must make judgments in your own programs.
Another problem is that the compiler does not strictly check the prototype of the Variable Parameter Function, which is detrimental to programming errors. If simple_va_fun () is changed:
Void simple_va_fun (int I ,...)
{
Va_list arg_ptr;
Char * s = NULL;
Va_start (arg_ptr, I );
S = va_arg (arg_ptr, char *);
Va_end (arg_ptr );
Printf ("% d % s \ n", I, s );
Return;
}
The variable parameter is char * type. When we forget to use two parameters to call this function, the core dump (Unix) or the page is invalid (Windows platform ). But there may also be no errors, but they are hard to find, which is not conducive to writing high-quality programs.
The following describes the compatibility of va macros.
System V Unix defines va_start as a macro with only one parameter: va_start (va_list arg_ptr );
Ansi c is defined as: va_start (va_list arg_ptr, prev_param );
If we want to use the definition of System V, we should use vararg. the macros defined in the h header file are incompatible with the macros of system V. We generally use ansi c, so the definition of ansi c is enough, it also facilitates program transplantation.
AnsiStandard Format |
Unix systemV compatibility |
|
Header file |
# Include <stdarg. h> 〉 |
# Include <varargs. h> 〉 |
Va_start |
Void va_start (argp, paran) va_list argp; |
Void va_start (argp) va_list argp; |
Va_arg |
Type va_arg (argp, type) va_list argp; |
Type va_arg (argp, type) va_list argp; |
Va_end |
Void va_end (argp) va_list argp; |
Void va_end (argp) va_list argp; |
Summary
The function principle of variable parameters is actually very simple, and the va series are defined by macro, and implementation is related to the stack. When writing a variable function's C function, it has both advantages and disadvantages. Therefore, we do not need to use variable parameters unless necessary. In C ++, we should use C ++ polymorphism to implement variable parameter functions, and try to avoid using C language.
Summary:
The stdarg. h header file specifies a type (va_list) and three macros (va_start, va_arg, and va_end ). They are function programming specifically for Variable Parameter tables.
Va_list: This array is used to store the information required by va_arg and va_end. When a called function uses a variable parameter table, it indicates a variable (param) of the va_list type.
Void va_start (va_list param, lastfix); the subprogram directs param to the first parameter of the variable parameter table that is sent to the function. Before calling va_arg and va_end, you must call va_start. The param parameter has been explained in the previous va_list. lastfix is the name of the last fixed parameter passed to the called function.
Type va_arg (va_list param, type); the extension expression of the subprogram gives it the same type and value as the next passed parameter. The variable param should be the same as that at va_start initialization. When va_arg is used for the first time, the first parameter in the table is returned, and the next parameter in the table is returned for each subsequent call.
Void va_end (va_list param); this macro is used by the called function to complete a normal return. It can modify param so that it cannot be used before re-calling va_start. Va_end is called only after all parameters are read by va_arg. Otherwise, unexpected results may occur.
From xt_chaoji's column