Indefinite parameters that year as a C + + language A special feature is respected by many people, but in fact, this technology does not apply a lot. In addition to formatting the output, I really didn't see much of the app. The main reason is that this technique is cumbersome and has a lot of side effects, and in general, overloaded functions are enough to replace it. However, since everyone is interested in it, I will briefly summarize its use and the common problems needing attention.
Principle:
When I was just learning C, the average person would first touch the printf function. With this function, you can print an indefinite number of variables to the screen, such as:
printf ("%d", 3);
printf ("%d,%d", 3,4);
The code above seems simple, but we actually need to solve a lot of problems. When we design printf, we don't know exactly how many parameters will be passed in. In this unknown situation, we need to address the following issues:
How to tell printf we're going to pass in a few parameters
printf How to access these parameters
After the function call is complete, how does the system release the parameters from the passed stack?
To solve these problems, we first explain the cdecl calling convention (see arguments with conventions), all functions that use indefinite arguments must either use the CDECL (global function) or the This call (class member function) calling convention. The Convention provides for parameter passing as follows:
The arguments go from right to left into the stack (that is, if you call F (a,b,c), C first into the stack, then B, and finally a into the stack)
The caller is responsible for cleaning up the stack
The 2nd one directly solves the third problem in the first three questions. Let's talk about the other two questions in detail.
Determine the number of parameters:
In a function, you typically have the following Prolog code:
00401020 Push EBP
00401021 mov Ebp,esp
00401023 Sub esp,48h
After executing the above code, the stack context in which the Func (a,b,c) function is located becomes the following layout:
Stack layout
Where EBP points to the address of the next word that holds the stack memory of the old Ebp, ebp+8 points to the EIP address, ebp+12 points to the first parameter of the function call, and between EBP and ESP is the space for temporary variables (that is, stack variables).
Note that due to the existence of the above Prolog code, it is easy to get the address of the first parameter via EBP, and we can get its actual position based on the type information (for example, the position of the first parameter offsets the size of the first parameter, for the type fixed before the parameter list). Is the address of the second parameter).
Note that there is a limit to the indefinite argument function, that the list of indeterminate arguments must be at the end of the argument list of the entire function. We can not define the following functions:
void func (int a, ..., int c)
All fixed-type parameters must appear at the beginning of the parameter list. So according to the previous discussion, we can get all types of fixed parameters.
When designing a function with an indeterminate argument list, we have two methods to determine exactly how many parameters are passed in.
Method 1 Indicates how many arguments are followed and their type in a fixed type parameter. printf is the method used, and its format parameter indicates the type of each subsequent parameter.
Method 2 is to specify an end parameter. This is generally the case where an indeterminate parameter has the same type, and we can specify a specific value to indicate the end of the parameter list. The following SUM function is an example:
int Sumi (int c, ...)
{
Va_list ap;
Va_start (AP,C);
int i;
int sum = c;
c = Va_arg (Ap,int);
while (0!=C)
{
sum = sum+c;
c = Va_arg (Ap,int);
}
return sum;
}
The code for using this function is:
int main (int argc, char* argv[])
{
int I=sumi (1,2,3,4,5,6,7,8,9,0);
return 0;
}
To access each parameter:
In fact, the previous article has told us how to access the indeterminate parameters. The Va_start and VA_ARG functions can be combined to access each function in turn, and they are actually macro functions.
The Vc6,va_start function is defined as:
#define _INTSIZEOF (N) ((sizeof (n) + sizeof (int)-1) & ~ (sizeof (int)-1))
#define VA_START (AP,V) (AP = (va_list) &v + _intsizeof (v))
where _intsizeof (n) calculates the minimum multiple of sizeof (int) greater than n, and if n=101, then _intsizeof (n) is 104.
After the va_start is executed, the AP points to the first 4-byte aligned address after the variable v. For example, the address of V is 0x123456 and the size of V is 13, then the next address aligned with the word boundary after V is 0x123456+0x0d=0x123463 and then adjusted to the next address that is aligned with 4 bytes, which is 0x123464.
The VA_ARG function is defined as:
#define VA_ARG (Ap,t) (* (t *) (AP + = _intsizeof (t))-_intsizeof (t)))
As with Va_start, the result is that the AP points to the next variable in the current variable.
In this way, we just use Va_start to assign the indeterminate parameter list to the AP at the beginning, and then use Va_arg to get the different parameters.
Potential problems:
With the indefinite argument list, there are two issues that require special attention.
The understanding of question 1 is relatively straightforward: when overloading a function, we cannot rely on the Indefinite argument list section to differentiate the function.
Suppose we define two overloaded functions as follows:
int func (int a, int b, ...)
int func (int a, int b, float c);
The above function causes the compiler not to know how to interpret func (1, 2, 3.3), because when the third parameter is a floating-point number, both implementations can satisfy the matching requirement. As a general rule, it is recommended that you do not overload the indeterminate parameter function.
Another problem is the type issue. In most cases, C and C + + variables are strongly typed, and the indefinite argument list is a special case.
When we call Va_arg, we specify the type of the next parameter, and at execution time, Va_arg is based on the information on the stack to find the corresponding parameter. There is no problem if the type we need is exactly the same as the real pass-in parameters, but if the types are different, then there is a big problem.
If the Sumi function above, we call by the following method:
int sum = Sumi (1, 2.2, 3, 0)
Note The second argument we passed a double type of 2.2, and we want Sumi to do an implicit type conversion when adding, and convert it to int for calculation. However, when we analyze this parameter, the call is:
C=va_arg (Ap,int)
Based on the definition of va_arg above, this macro is translated into:
#define VA_ARG (Ap,t) (* (int *) (AP + = _intsizeof (int))-_intsizeof (int)))
If the following + = calculates the correct address, it will eventually become
* (int*) addr
If you want to get the correct integer value, you must require that the address where the addr is located is a true int type. But when we pass in a double, in fact its memory layout and int are completely different, so we don't get the integer we need. Interested friends can be tested with the following simple code:
Double A;
a=1.1;
int b = * (int*) & A;
Therefore, when we call a function that has an indefinite argument list, do not expect the system to do an implicit type conversion, the system will not do such a check or conversion, you must give the parameter type exactly as you want.
Application of indefinite parameters