From: http://blog.csdn.net/guanzhongs/archive/2007/04/04/1551747.aspx#550577
C/C ++ supports variable parameter number function definitions, which are related to the order in which C/C ++ function parameters are called,
First, reference a text clip from other netizens to describe the function call and the parameter Stack:
------------ Reference starts ------------
C supports variable parameters. Here, C supports variable parameters. The most common example is
It is a series of functions that we are very familiar. We also know that the parameters for function calling are from right to left.
. If the common form of variable parameter functions is:
F (P1, P2, P3 ,...)
Then, the order in which parameters are pushed to the stack (and the output stack) is:
...
Push p3
Push p2
Push p1
Call F
Pop p1
Pop p2
Pop p3
...
I can conclude that if variable parameter functions are supported, the order in which parameters go to the stack is almost inevitable.
Right to left. In addition, the parameter output stack cannot be completed by the function itself, but by the caller.
The second half of this conclusion is not easy to understand, because the function itself does not know how many parameters the caller has passed,
The caller knows, so the caller should be responsible for putting all parameters out of the stack.
In the general form of variable parameter functions, the left side is the determined parameter, and the right side ellipsis represents the unknown parameter section.
. For a fixed parameter, its position on the stack must also be determined. Otherwise, the parameter is determined.
It cannot be located or found, so the function cannot be correctly executed. The position of the Measurement Parameter on the stack is
How far is the call point (call f. The location of a fixed parameter on the stack. It should not
Depends on the specific number of parameters, because the number of parameters is unknown!
Therefore, you can only select a parameter that has been determined and there is a definite distance (closer) from the function call point ). Meet this
Condition. Only the parameter stack entry follows the rules from right to left. That is to say, after the parameters identified on the left are pushed to the stack, the function is removed.
The call point has a definite distance (the leftmost parameter is last written into the stack, closest to the function call point ).
In this way, when the function starts execution, it can find all the determined parameters. According to the logic of the function, it is negative.
Find and explain variable parameters after the call point (which is far away from the call point). This usually depends on
Parameter values (typical formats such as prinf () functions are interpreted. Unfortunately, this method is vulnerable ).
It is said that in Pascal, the parameter is stack-pressed from left to right, opposite to C. Only fixed parameter functions such as Pascal are supported.
Number language, which has no problems caused by variable parameters. Therefore, it can select which parameter can be used for Stack import.
Even the parameter output stack is completed by the function itself, rather than the caller, because the type and quantity of function parameters
Is completely known. This method is more efficient than C, because it consumes lessCodeAmount (in C,
Each time a function is called, the parameter output code is generated ).
C ++ supports variable parameters for functions to be compatible with C. But in C ++, a better choice is often a function.
Overload.
------------ End of reference ------------
According to the description above, we can check the definitions of functions such as printf () and sprintf () to verify this:
_ Cribd int _ cdecl printf (const char *,...);
_ Cribd int _ cdecl sprintf (char *, const char *,...);
When defining these two functions, the _ cdecl keyword __cdecl is used to specify the function call rules:
The caller is responsible for clearing the call stack. parameters are passed through the stack, and the inbound order is from right to left.
Next, let's take a look at how the printf () function uses the variable number parameter. The following is an example from msdn,
Only the ANSI system compatible code is referenced. For the UNIX system code, see msdn.
------------ Sample code ------------
# Include <stdio. h>
# Include <stdarg. h>
Int average (INT first ,...);
Void main (void)
{
Printf ("average is: % d" N ", average (2, 3, 4,-1 ));
}
Int average (INT first ,...)
{
Int COUNT = 0, sum = 0, I = first;
Va_list marker;
Va_start (Marker, first);/* initialize variable arguments .*/
While (I! =-1)
{
Sum + = I;
Count ++;
I = va_arg (Marker, INT );
}
Va_end (Marker);/* reset variable arguments .*/
Return (sum? (Sum/count): 0 );
}
------------ Code ended ------------
The code above function is used to calculate the average number. The function allows users to enter multiple integer parameters, which must be the last parameter.
-1 indicates that the input is complete, and then the average calculation result is returned.
The logic is simple. First, define
Va_list marker;
Parameter List, and then call va_start () to initialize the parameter list. Note that not only marker is used for va_start () calls
This parameter list variable also uses the first parameter, indicating the initialization of the parameter list and the first parameter given by the function.
It is critical to determine whether a parameter is related. The cause will be displayed in subsequent analysis.
After va_start () is called for initialization, you can call the va_arg () function to access the parameters in each parameter list. Note va_arg ()
The second parameter specifies the type (INT) of the return value ).
WhenProgramAfter all the parameter access is completed, call the va_end () function to end the parameter list access.
It seems that it is easy to access the variable number parameter, that is, using va_list, va_start (), va_arg (), va_end ()
Such a type and three functions. However, the function variable number parameter mechanism is still confusing. It seems necessary
Continue to explore in depth to find the exact answer.
Find the va_list, va_start (), va_arg (), and va_end () definitions in the... "vc98" include "stdarg. h file.
The code in. H is as follows (only extract the ANSI compatible part of the code, UNIX and other systems are slightly different in implementation, interested friends can
Research by yourself ):
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)
From the code, we can see that va_list is just a type escape, which is actually a pointer defined as a char * type. This is
Memory is accessed in bytes.
The other three functions are actually three macro definitions, and they are slow. Let's take a look at the macro definition _ intsizeof in the middle:
# DEFINE _ intsizeof (N) (sizeof (n) + sizeof (INT)-1 )&~ (Sizeof (INT)-1 ))
This macro is used to calculate the size of a given variable or Type N after its byte alignment by integer length ). In a 32-bit System
Int occupies 4 bytes and 16-bit Systems occupies 2 bytes.
Expression
(Sizeof (n) + sizeof (INT)-1)
If sizeof (n) is smaller than sizeof (INT ),
The result value of is one to the left of the binary value of sizeof (n.
For example, sizeof (short) + sizeof (N)-1 = 5
The binary value of 5 is 0x00000101, And the binary value of sizeof (short) is 0x00000010. Therefore, the binary value of 5 is equal to the binary value of 2.
To the left.
Expression
~ (Sizeof (INT)-1)
Generate a mask (mask) to remove the "zero Header" section of the previous calculated value.
For example ,~ (Sizeof (INT)-1) = 0x00000011 (thanks for the reminder from glietboys. Here it should be 0xffffff00)
Perform the "and" operations on the same 5 binary 0x00000101 to obtain 0x00000100, that is, 4, and directly calculate sizeof (short) to obtain 2.
In this way, expressions such as _ intsizeof (short) can be used to obtain other types of bytes that are aligned according to the length of integer bytes.
The reason why int-type bytes are used for alignment is that the pointer variable in C/C ++ is actually an integer value with the same length as Int,
The offset of the pointer is required by the following three macro operations.
If you are interested in byte alignment content in programming, visit the Internet to refer to otherArticle.
Continue, the following three macro definitions:
First:
# Define va_start (AP, V) (AP = (va_list) & V + _ intsizeof (v ))
This is used in programming.
Va_list marker;
Va_start (Marker, first );
We can see that the role of the va_start macro is to make the given parameter list pointer (Marker), according to the first to determine the type of the parameter (first)
The pointer length is offset back to the corresponding position. When calculating the offset, the previous _ intsizeof (n) macro is used.
Second:
# Define va_arg (AP, t) (* (T *) (AP + = _ intsizeof (t)-_ intsizeof (t )))
At first glance, it is a bit confusing. The (AP + = _ intsizeof (t)-_ intsizeof (t) expression minus one and minus one does not work for the returned value.
That is, the returned values are all AP values. Why?
The original return value for this calculation is one aspect. On the other hand, remember that the call of the three macros va_start (), va_arg (), and va_end is correlated.
AP is a pointer to the parameter list given when va_start () is called.
(AP + = _ intsizeof (t)-_ intsizeof (t)
The expression is not only used to return the address of the currently directed parameter, but also used to direct the AP to the next parameter (note that the next parameter for AP jump is,
Is calculated according to the _ intsizeof length of the type T ).
Third:
# Define va_end (AP) (AP = (va_list) 0)
This is easy to understand, but the AP pointer is set to null, And the read is counted as the end of the parameter.
So far, the mechanism for changing the number of function parameters in C/C ++ is clear. Note the following points:
In the process of using va_arg () to jump to the pointer to read parameters, there is no way to determine whether the obtained next pointer is a valid address.
There is no clear picture of the number of parameters to be read, which is a risk of changing the number of parameters. The preceding example of average calculation
Must provide a special value (-1) at the end of the parameter list to indicate the end of the parameter list.
If this rule is not followed, the pointer access will be out of bounds.
Some may ask that the printf () function does not provide such special values for identification.
Don't worry, printf () uses another parameter number recognition method, which may be relatively hidden. Note that his first definite parameter is
Format String, which contains parameter descriptors such as "% d" and "% s". The printf () function is parsing the format character
When reading a string, you can determine the following parameters based on the number of parameter descriptors. We may wish to do the following experiment:
Printf ("% d, % d" N ", 1, 2, 4, 5 );
Actually, more parameters are provided than the preceding parameter descriptors. The result of this execution is:
1, 2, 3, 4
That is, printf () considers that there are only four parameters following the format string. Then we will try again:
Printf ("% d, % d" N ", 1, 2, 3 );
The actual provided parameter is less than the given parameter descriptor, so the execution result is (if there is no exception)
1, 2, 3, 2367460
In this case, the execution results of each person may be different because the pointer to the last parameter has been read to an Invalid Address. This is also
Special attention is required when using functions such as printf.
Summary:
When using function parameters with variable numbers, you need to pay more attention to them. I personally suggest avoiding this mode. For example, the previous calculation average
Number, rather than passing a series of values to the function using arrays or other lists as parameters. On the one hand, it is easy to get out
Currently, pointer access is out of bounds. On the other hand, when calling a function, you must write all the calculated values in sequence as parameters in the code.
Although in this case, this function is useful in some places, such as string formatting and synthesis, such as the printf () function.
I often use a self-written writelog () function to record file logs. The definition is the same as that of printf ().
Convenience, such:
Writelog ("User % s, logon times % d", "Guanzhong", 10 );
The content written in the file is
User Guanzhong: 10 logins
Programming LanguageFollow the basic rules. In short, after a thorough understanding, you can choose a good habit that suits your needs.