When the number of parameters in your function is unknown, you can use the macro above for dynamic processing, which undoubtedly adds flexibility to your program.
Example:
◎ Usage 1:
Func (Type para1, Type para2, Type para3 ,...)
{
/****** Step 1 ******/
Va_list ap;
Va_start (ap, para3); // you must specify the parameter before "...".
/****** Step 2 ******/
// At this time, the ap points to the first variable parameter.
// Call va_arg to obtain the value
Type xx = va_arg (ap, Type );
// The Type must be the same, for example:
// Char * p = va_arg (ap, char *);
// Int I = va_arg (ap, int );
// If multiple parameters continue to call va_arg
/****** Step 3 ******/
Va_end (ap); // For robust!
}
◎ Usage 2:
CString AppendString (CString str1,...) // a function connecting strings. The number of parameters can be dynamically changed.
{
LPCTSTR str = str1; // str must be of the pointer type, because the va_arg macro returns the pointer of your parameter, but if your parameter is of the int or other simple // single type, you do not need to be a pointer because the variable name is actually a pointer.
CString res;
Va_list marker; // your linked list
Va_start (marker, str1); // initialize your marker linked list
While (str! = "ListEnd") // ListEnd: The End mark of the parameter, which is very important. You must specify
{
Res + = str;
Str = va_arg (marker, CString); // gets the next pointer
}
Va_end (marker); // end, used with va_start
Return res;
}
Int main ()
{
CString str = AppendString ("xu", "zhi", "hong", "ListEnd ");
Cout <str. GetBuffer (str. GetLength () <endl;
Return 0;
}
Output xuzhihong
CString AppendString (CString str1,...), because the parameters of the connection string can be dynamically changed, you do not know the number of strings to be connected, so you can use... . However, you must note that your function must have a parameter as a flag to indicate the end. Otherwise, an error will occur. In the preceding example, the ListEnd is used as the Terminator. In addition, va_arg returns the pointer to your parameter content. The preceding example runs on the console that supports the MFC program.
The prototype declaration format of variable parameter functions is:
Type VAFunction (type arg1, type arg2 ,... );
Parameters can be divided into two parts: fixed parameters with fixed numbers and optional parameters with variable numbers. A function requires at least one fixed parameter. The fixed parameter must be declared in the same way as a common function. The number of optional parameters is unknown "... . The fixed parameters and optional parameters are the same as the parameter list of a function.
Let's take a look at the functions of each va_xxx using the simple example 2 above.
Va_list arg_ptr: defines a pointer to a variable number of parameter lists;
Va_start (arg_ptr, argN): sets the parameter list pointer arg_ptr to the first optional parameter in the function parameter list. Description: argN is a fixed parameter located before the first optional parameter (or, the last fixed parameter ;... The order of parameters in the function parameter list in the memory is the same as that in the function declaration. If the declaration of a va function is void va_test (char a, char B, char c ,...), Then its fixed parameters are a, B, c in sequence, and the last fixed parameter argN is c, so it is va_start (arg_ptr, c ).
Va_arg (arg_ptr, type): return the parameter indicated by the pointer arg_ptr In the parameter list. The return type is type, and the pointer arg_ptr points to the next parameter in the parameter list.
Va_copy (dest, src): the types of dest and src are va_list. va_copy () is used to copy the parameter list pointer and initialize dest as src.
Va_end (arg_ptr): clears the parameter list. The parallel parameter pointer arg_ptr is invalid. Note: After the pointer arg_ptr is set to invalid, you can restore arg_ptr by calling va_start () and va_copy. Each time va_start ()/va_copy () is called, a corresponding va_end () must match it. The parameter pointer can be freely moved back and forth in the parameter list, but must be in va_start ()... Within va_end.
The implementation of the va function is the use and control of parameter pointers.
Typedef char * va_list; // va_list definition on the x86 Platform
The fixed parameter section of the function can be obtained directly from the parameter name when the function is defined. For the optional parameter section, first point the pointer to the first optional parameter, and then move the pointer backward, determine whether all parameters have been obtained based on the comparison with the end mark. Therefore, the ending mark in the va function must be agreed in advance. Otherwise, the pointer will point to an invalid memory address, resulting in an error.
Here, the moving Pointer Points to the next parameter, so what is the offset when the pointer is moved? There is no specific answer, because the memory alignment problem is involved here, memory alignment is closely related to the specific hardware platform used. For example, the well-known 32-bit x86 Platform requires that all variable addresses must be multiples of 4 (sizeof (int) = 4 ). In the va mechanism, macro _ INTSIZEOF (n) is used to solve this problem. Without these macros, va portability is impossible.
First, we will introduce macro _ INTSIZEOF (n), which is used to determine the size of the memory occupied by the variable, which is the basis for implementing va.
# Define _ INTSIZEOF (n) (sizeof (n) + sizeof (int)-1 )&~ (Sizeof (int)-1 ))
# Define va_start (ap, v) (ap = (va_list) & v + _ INTSIZEOF (v) // The first optional parameter address
# Define va_arg (ap, t) (* (t *) (ap + = _ INTSIZEOF (t)-_ INTSIZEOF (t) // The next parameter address
# Define va_end (ap) (ap = (va_list) 0) // set the pointer to invalid
The following table is for the int TestFunc (int n1, int n2, int n3,…) function ,...)
Memory stack when parameters are passed. (The default parameter passing method of the c compiler is _ cdecl .)
The call to this function is int result = TestFunc (a, B, c, d. e), where e is the end mark.
We can clearly see the reason for writing the va_xxx macro.
1. va_start. To obtain the address of the first optional parameter, we have three methods:
A) = & n3 + _ INTSIZEOF (n3)
// Address of the last fixed parameter + Memory occupied by this parameter
B) = & n2 + _ INTSIZEOF (n3) + _ INTSIZEOF (n2)
// The address of a fixed parameter in the middle + the sum of memory occupied by all fixed parameters after this parameter
C) = & n1 + _ INTSIZEOF (n3) + _ INTSIZEOF (n2) + _ INTSIZEOF (n1)
// The address of the first fixed parameter + the sum of memory occupied by all fixed parameters
From the perspective of compiler implementation, Method B), method C) in order to find the address, the compiler still needs to know how many fixed parameters and their size, without breaking down the problem to the simplest, therefore, it is not A clever path and will not be adopted. Relatively speaking, the two values calculated in method A can be completely determined. Va_start () uses the) method to accept the last fixed parameter. The result of calling va_start () always points the pointer to the address of the next parameter and uses it as the first optional parameter. When calling va_start () in a function with multiple fixed parameters, if the last fixed parameter is not used, the number of optional parameters has increased for the compiler, it will bring unexpected errors to the program. (Of course, if you think you have a thorough understanding of the pointer, you can use it to complete some excellent (efficient) Code. However, this greatly reduces the readability of the Code .)
Note: The macro va_start is used to operate on the parameter address. The parameter address must be valid. Some invalid addresses cannot be treated as fixed parameter types. For example, for the register type, its address is not a valid memory address value; arrays and functions are not allowed, and their length is a problem. Therefore, these types cannot be used as parameters of the va function.
2. va_arg has two roles: return the current parameter and point the parameter pointer to the next parameter.
The definition of the va_arg macro is awkward. If we split it into two statements, we can clearly see the two responsibilities it has accomplished.
# Define va_arg (ap, t) (* (t *) (ap + = _ INTSIZEOF (t)-_ INTSIZEOF (t) // The next parameter address
// Split (* (t *) (ap + = _ INTSIZEOF (t)-_ INTSIZEOF (t):
/* Pointer ap points to the address of the next parameter */
1. ap + = _ INTSIZEOF (t); // currently, the ap is directed to the next parameter.
/* Ap obtains the address of the current parameter after subtracting the size of the current parameter, and then returns its value after forced type conversion */
2. return * (t *) (ap-_ INTSIZEOF (t ))
Looking back to formatting commands such as % d % s of the printf/scanf series functions, we can easily understand their usage-explicitly specify the type of mandatory conversion of parameters.
(Note: printf/scanf is not implemented using va_xxx, but the principle is consistent .)
3. va_end is simple, just to void the pointer.
# Define va_end (ap) (ap = (va_list) 0) // x86 Platform
4. conciseness, flexibility, and danger
From the implementation of va, we can see that the rational use of pointers can fully express the concise and flexible features of the C language, which makes it hard to admire the power and efficiency of C. It is undeniable that too much free space for programmers will inevitably reduce program security. In va, va_arg must be used to traverse all parameters passed to the function in sequence. There are two risks:
1) determine the parameter type.
Va_arg is not so flexible as to check the type, because it is forced type conversion, va_arg forcibly converts the content pointed to by the current pointer to the specified type;
2) End mark. If there is no end sign, va will return the content in the memory in sequence based on the default type until the access to illegal memory and exit with an error. In Example 2, SqSum () is used to calculate the square sum of a natural number. Therefore, I use negative numbers and 0 as its end sign. For example, scanf regards the received carriage return as the ending mark. The well-known printf () uses '\ 0' to process the string as the ending mark, I cannot imagine how the code would look like if the string in C is not '\ 0'. It is estimated that the most popular character array or malloc/free would be at that time.
Allowing Random Access to memory leaves the possibility of attacks to malicious users. After processing a string carefully designed by the cracker, the program will jump to some malicious code areas for execution, so that the cracker can achieve its attack purpose. (Common exploit attacks) Therefore, you must disable Random Access to the memory and strictly control the access boundary of the memory.
Question about va_list and vsnprintf Output Functions
Source: ChinaUnix blog Date: 2006.11.16 (0 comments in total) I want to comment
Va_list ap; // declare a variable to convert the parameter list
Va_start (ap, fmt); // initialize the variable
Va_end (ap); // end Variable list, which is used in pairs with va_start.
Parameters can be retrieved Based on va_arg (ap, type)
Output programs that have been successfully debugged
# Include
# Include
# Define bufsize 80
Char buffer [bufsize];
Int vspf (char * fmt ,...)
{
Va_list argptr;
Int cnt;
Va_start (argptr, fmt );
Cnt = vsnprintf (buffer, bufsize, fmt, argptr );
Va_end (argptr );
Return (cnt );
}
Int main (void)
{
Int inumber = 30;
Float fnumber = 90.0;
Char string [4] = "abc ";
Vspf ("% d % f % s", inumber, fnumber, string );
Printf ("% s \ n", buffer );
Return 0;
}