C/C ++ supports variable parameter functions

Source: Internet
Author: User

Http://hi.baidu.com/ldlmeiufvkgmtzr/item/8074dd1349b739fddceecafa

I. Why do I need a Variable Parameter Function?

Generally, when programming, the number of formal parameters in the function is usually determined, and all actual parameters corresponding to the formal parameters should be given in sequence during the call. However, in some cases, the number of parameters of a function can be determined as needed. Therefore, variable parameter functions are introduced in C language. This is also a powerful aspect of C. Some other languages, such as Fortran, do not have this feature.

Typical examples of variable parameter functions include printf () and scanf.

Ii. How can C/C ++ implement variable parameter functions?

To support variable parameter functions, the C language introduces a new call protocol, that is, the C language call Convention _ cdecl. This call Convention is used by default when C/C ++ is used for programming. To use other call conventions, you must add other keyword declarations. For example, if Win32 APIs use Pascal call conventions, the _ stdcall keyword must be added before the function name.

When C is used to call the stack, the function parameters are from right to left, and the number is variable. Because the function body cannot know the number of parameters passed in advance, the function caller must be responsible for Stack cleaning when using this convention. For example:

// C call the agreed Function
Int _ cdecl add (int A, int B)
{
Return (A + B );
}

Function call:
Add (1, 2 );
// The assembly code is:
Push 2; parameter B enters the stack
Push 1; parameter A goes into the stack
Call @ add; call a function. In fact, the expressions used by the compiler to locate functions are omitted here.
Add ESP, 8; the caller is responsible for Stack clearing

If the call protocol used to call a function is inconsistent with the declared in the function prototype, stack errors may occur. This is another topic and I will not elaborate on it here.

In addition, the C/C ++ compiler supports variable parameter functions in the form of macros. These macros include va_start, va_arg, and va_end. This is done to increase program portability. Shield the differences caused by different hardware platforms.

All macros that support variable parameter functions are defined in stdarg. h and varargs. h. For example, in the standard ANSI format, these macros are defined:

Typedef char * va_list; // string pointer

# 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)

Macro _ intsizeof is used to align the pointer according to the integer bytes, because under the C call protocol, the parameter stack is an integer byte (pointer or value ).


3. define such functions.

Variable Parameter functions are defined in different forms in different systems.

1. In the ANSI standard format, the prototype declaration of a function with a variable number of parameters is:

Type funcname (type para1, type para2 ,...);

There are three requirements for this definition:

In general, this form requires at least one common form parameter, which is defined by three. So "..." does not mean omitted, but is part of the function prototype. Type is the type of function return values and formal parameters.
For example:

Int myprintf (char const * FMT ,...);

However, we can also define the function as follows:

Void myfunc (...);

However, in this way, we cannot use the function parameters, because we cannot extract each parameter through the macro mentioned above. Therefore, unless you do not use any parameters in the parameter table in your function code, you must use at least one common parameter in the parameter table.

Note: variable parameters can only be placed at the end of the function parameter table. This cannot be the case:

Void myfunc (..., int I );

2. When the declaration method is adopted in a Unix-compatible system, the prototype of a function with a variable number of parameters is:

Type funcname (va_alist );

However, va_dcl must be added to the function name when the function is implemented. For example:

# I nclude <varargs. h>
Int average (va_list );

Void main (void)
{
... // Code
}

/* UNIX compatibility form */
Int average (va_alist)
Va_dcl
{
... // Code
}

This form does not require any common form parameters. Type is the type of the function return value. Va_dcl is a detailed declaration of the parameter va_alist in the function prototype declaration. It is actually a macro definition. The definition of va_dcl varies slightly depending on the platform.

In varargs. H, the va_dcl definition is followed by a semicolon. Therefore, you do not need to add a semicolon after va_dcl when implementing a function.


3. Use the header file stdarg. the program written by H complies with the ANSI standard and can run on various operating systems and hardware. The header file varargs is used. H is only used for compatibility with previous programs. The basic principles of the two methods are the same, but there are some minor differences in syntax form. So stdarg. H is generally used for programming. All the sample code below adopts the ANSI standard format.


Iv. Basic usage of Variable Parameter Functions

The following examples illustrate how to define and call a Variable Parameter Function.

// ==================================== Example program 1 ====== ================
# I nclude <stdio. h>
# I nclude <string. h>
# I nclude <stdarg. h>

/* The function prototype Declaration requires at least one definite parameter. Note the ellipsis in brackets */
Int demo (char *,...);

Void main (void)
{
Demo ("Demo", "this", "is", "A", "Demo! "," \ 0 ");
}

Int demo (char * MSG ,...)
{
Va_list argp;/* define the structure for saving function parameters */
Int argno = 0;/* Number of record parameters */
Char * para;/* stores the retrieved string parameters */

// Use the macro va_start to point argp to the first optional parameter passed in,
// Note that MSG is the final parameter in the parameter table, not the first parameter in the parameter table.
Va_start (argp, MSG );

While (1)
{
// Retrieve the current parameter, type: char *
// If the correct type is not given, an incorrect parameter is obtained.
Para = va_arg (argp, char *);

If (strcmp (para, "\ 0") = 0)/* use an empty string to indicate the end of the parameter input */
Break;
Printf ("parameter # % d is: % s \ n", argno, para );
Argno ++;
}
Va_end (argp);/* Set argp to null */
Return 0;
}

// Output result
Parameter #0 is: this
Parameter #1 is: Is
Parameter #2 is:
Parameter #3: Demo!

Note that the first parameter is not used in the above example. The following example uses all parameters

// ========================================= Example Program 2 ====== ================

# I nclude <stdio. h>
# I nclude <stdarg. h>
Int average (INT first,...); // enter several Integers to calculate their average value.

Void main (void)
{
/* Call three integers (-1 indicates the end )*/
Printf ("average is: % d \ n", average (2, 3, 4,-1 ));

/* Call four integers */
Printf ("average is: % d \ n", average (5, 7, 9, 11,-1 ));

/* Calls with only the terminator */
Printf ("average is: % d \ n", average (-1 ));
}

/* Function that returns the average of several integers */
Int average (INT first ,...)
{
Int COUNT = 0, sum = 0, I = first;
Va_list marker;

Va_start (Marker, first); // Initialization
While (I! =-1)
{
Sum + = I; // Add the first parameter first
Count ++;
I = va_arg (Marker, INT); // take the next Parameter
}
Va_end (Marker );
Return (sum? (Sum/count): 0 );
}

// Output result
Average is: 3
Average is: 8
Average is: 0


V. Transmission of variable parameters

Someone asked this question. If I have defined a Variable Parameter Function and want to call other variable parameter functions inside the function, how can I pass parameters? In the above example, the macro va_arg is used to extract parameters one by one. Can we directly pass them to other functions without extraction?

Let's first look at the implementation of printf:

Int _ cdecl printf (const char * format ,...)
{
Va_list Arglist;
Int buffing;
Int retval;

Va_start (Arglist, format); // Arglist points to the first parameter after format

... // Do not care about other code
Retval = _ output (stdout, format, Arglist); // pass the format and parameters to the output function.

... // Do not care about other code
Return (retval );
}

First, we will imitate this function to write one:

# I nclude <stdio. h>
# I nclude <stdarg. h>

Int mywrite (char * FMT ,...)
{
Va_list Arglist;
Va_start (Arglist, FMT );
Return printf (FMT, Arglist );
}

Void main ()
{
Int I = 10, j = 20;
Char Buf [] = "this is a test ";
Double F = 12.345;
Mywrite ("string: % s \ nint: % d, % d \ nfloat: % 4.2f \ n", Buf, I, j, F );
}

Run the command to see if there are hundreds of errors. Based on macro definition, we know that Arglist is a pointer pointing to the first variable parameter, but all the parameters are in the stack, so Arglist points to a position in the stack, with the value of Arglist, we can directly view the content in the stack:

Arglist-> points to the stack, including

0067fd78 E0 FD 67 00 // point to the string "this is a test"
0067fd7c 0a 00 00 // integer I value
0067fd80 14 00 00 00 // integer J value
0067fd84 71 3D 0a D7 // double variable F, which occupies 8 bytes
0067fd88 A3 B0 28 40
0067fd8c 00 00 00

If you directly call printf (FMT, Arglist); just import the Arglist pointer value 0067fd78 into the stack, and then import the format string into the stack, which is equivalent to calling:

Printf (FMT, 0067fd78 );

Naturally, such a call will certainly produce errors.

Can we extract parameters one by one and pass them to other functions? First, we should consider the problem of passing all the parameters at a time.

If the system library function is called, this is impossible. Because the extracted parameters are in the running state, and the parameters are determined during the compilation. The compiler cannot predict the running state and give correct parameters to the stack code. While we can extract every parameter in the running state, we cannot press all the parameters on the stack at a time, even if we use assembly code to implement it, it is very difficult, this is because it is not only a simple push code.

If the function that accepts parameters is also written by ourselves, we can naturally import the Arglist pointer into the stack, and then parse the parameters in the Arglist pointer in the function and extract them one by one for processing. However, this does not seem to make any sense. On the one hand, this function does not need to be made into a Variable Parameter Function, on the other hand, it is easier to parse parameters in the first function and then process them?

The only thing we can do is parse parameters one by one, and then call other variable parameter functions cyclically, passing a parameter each time. There is another problem here, that is, the transmission of immutable parameters in the parameter table. In some cases, it cannot be simply transmitted. The preceding example shows that when we parse parameters, you also need to parse the format string:

# I nclude <windows. h>
# I nclude <stdio. h>
# I nclude <stdarg. h>

// Test this. Make a joke.
Void T (...)
{
Printf ("\ n ");
}

Int mywrite (char * FMT ,...)
{
Va_list Arglist;
Va_start (Arglist, FMT );

Char temp [255];
Strcpy (temp, FMT); // copy the Format String
Char format [255];

Char * P = strchr (temp, '% ');
Int I = 0;
Int iparam;
Double fparam;
While (P! = NULL)
{
While (* P <'A' | * P> 'Z') & (* P! = 0) P ++;
If (* P = 0) break;
P ++;

// Formatted string
Int nchar = p-temp;
Strncpy (format, temp, nchar );
Format [nchar] = 0;
// Parameters
If (Format [nChar-1]! = 'F ')
{
Iparam = va_arg (Arglist, INT );
Printf (format, iparam );
}
Else
{
Fparam = va_arg (Arglist, double );
Printf (format, fparam );
}

I ++;
If (* P = 0) break;
Strcpy (temp, P );
P = strchr (temp, '% ');
}
If (temp [0]! = 0)
Printf (temp );

Return I;

}

Void main ()
{
Int I = 10, j = 20;
Char Buf [] = "this is a test ";
Double F = 123.456;
Mywrite ("string: % s \ nint: % d, % d \ nfloat: % 4.2f \ nend", Buf, I, j, F, 0 );
T ("AAA", I );
}

// Output:
String: this is a test
INT: 10, 20
Float: 123.46
End

Of course, the resolution here is not perfect.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.