Deep Exploration of variable length parameters of C-language functions

Source: Internet
Author: User
Tags variadic

Usually the number of arguments we use for the C function is fixed, but it is not fixed. such as printf () and scanf (). How to implement a variadic function by yourself is a bit tricky.

Our most common use is to define a macro, using printf or PRINTK, as follows

#define WWLOGK (FMT, args ...) printk (FMT, # # args)

Now we're going to do it ourselves. A variable parameter function, which is followed by the analysis principle. First look at an example:

#include <stdio.h>

#include <stdarg.h>

int Sum(int first, int second, ...) //When the transfer function cannot be listed for all real

The parameter table can be specified with an ellipsis when the type and number of arguments

{

int sum = 0, t = First;

Va_list VL;

Va_start (VL, first);

while (t! =-1) {

sum + = t;

t = va_arg (VL, int); Converts the current argument to an int type

}

Va_end (VL);

return sum;

}

int main (int argc, char* argv[])

{

printf ("The sum is%d\n", SUM (30, 20, 10,-1)); -1 is the parameter end flag

return 0;

}

There are several variables that need to be explained. Va_list, Va_start (), Va_end and Va_arg.

Va_list: This type of variable is used to access a mutable parameter, which is actually a pointer.

Va_start (): is a macro that gets the parameters in the argument list so that VL points to the first mutable parameter and ends with the call to Va_end () when finished.

Va_end: Also a macro that ends the call to Va_start ().

Va_arg: A macro that is used to get a value removed from the argument list.

In the Linux source code, include/acpi/platform/acenv.h, the header file is described in detail.

1, va_list VL;

typedef char *va_list; Defines a new type that points to a pointer to a string. The real intention is that when the pointer moves is in "1" units, because sizeof (char) = 1; that is, the char type takes up one byte and the int type is 4 bytes.

2. Va_start (VL, first) makes the VL point to a variable parameter, i.e..

#define VA_START (AP, A) (void) (AP) = (((char *) & (A)) + (_bnd (a,_aupbnd)))

#define _BND (x, BND) ((sizeof (x) + (BND)) & (~ (BND)))

_bnd (X, BND) is defined primarily for some systems that require memory alignment, and the purpose of this macro is to get the actual memory size of the last fixed parameter. Directly with sizeof also has no effect.

#define _AUPBND (sizeof (Acpi_native_int)-1)

Where Acpi_native_int is determined by the hardware platform, that is, the width of an int type, that is, the byte.

typedef S64 Acpi_native_int;

typedef S32 ACPI_NATIVE_INT;

typedef int S32;

typedef long Long S64;

Here's a look at the macro code to explain:

When the process runs, the variable is pressed into the stack, and (char *) & (A) points to first, which is the top of the stack;

Using macro _bnd (A,_AUPBND) is primarily for some systems that require memory to be aligned in integer bytes, because the parameters in the stack are the whole number section (pointer or value) under the C Call protocol--- so-called alignment, to Intel80x86 The machine requires that each variable address is a multiple of sizeof (int) . Then why are you aligning? Because in alignment, the CPU runs much faster.

       Example: When a long number (medium long1) is aligned in memory with exactly the word boundary of the memory, the CPU accesses this number only once to access the memory, and when a long Type number (in Long2) in the memory of the position across the word boundary, the CPU accesses the number of times need to access memory, such as I960CX access to the number of read memory three times (a byte, a short, a byte, by the CPU's micro-code execution, the software transparent), So the alignment of the CPU is significantly faster than the operating efficiency.
1       8       16      24      32   
------------------------------
| long1 | long1 | long1 | l ong1 |
------------------------------
|        |         |         | Long2 |
------------------------------
| long2 | long2 | long2 |        |
------- ------- ------- ---------

Because the _aupbnd is an int width, then _bnd (A, _aupbnd) means that the data of an int width is not enough, or an int width is skipped. such as char, short type of data sizeof after 1 and 2, assuming now is the 32-bit system char type, _BND (X, BND) = (1 + 3) & (TO) = 0x4; 2 as well. Because the 32-bit system int width is 4. After skipping, the AP points to second, and first's value is already stored in T. It is necessary to note that the first is specified as the int type. If it is a different type (such as char), an error occurs because 4 bytes are skipped first.

Figure 1 Structure of the stack

Also here are some points to note:

    1. Because the C language presses the stacking order from right to left.
    2. The expansion direction of the stack is extended downward, so the bottom of the stack is the high address, and the top of the stack is the low address.

      For example, suppose that f (a,b,c,d) presses the stack from right to left, then D should be the first to stack, and a is the last to go to the stack, so the address of D should be higher than a. On the intel+ Windows machine, the function stack is in the direction of downward, the memory address of the top pointer is lower than the stack pointer, so the data of the advanced stack is stored at the high address of the memory.

    3. C language when the stack, the first stack is the main function of the address of the first instruction, followed by the function parameters and local variables.

As described above, after Va_start (Vl,first), the VL points to the first mutable parameter after a . We all know that Pascal 's parameters are left-to-right when they are in the stacking order, but the C language will be from right to left . Why is it? It's also a place where C is higher than Pascal.--c language supports the variable-length parameter function in the order in which the parameters are placed in the stack!

In order to support variadic functions, the C language introduces a new calling protocol, the C language calling convention __cdecl. This calling convention is used by default when programming in the C + + language. If you want to take other calling conventions, you must add additional keyword declarations, such as the WIN32 API using the Pascal calling convention, which must precede the function name with the __stdcall keyword. When using the C calling convention, the parameters of the function are entered from right to left and the number is variable. Since the function body cannot know in advance the number of arguments passed in, the use of this Convention must be performed by the function caller responsible for stack cleanup.

3, #define VA_ARG (AP, T) (* (T *) ((AP) + = (_bnd (t, _aupbnd))-(_bnd (T,_ADNBND))))

Expand this macro to see more clearly:

    1. AP = ap + _bnd (T,_AUPBND)--first skips the AP to the specified width, which points to the next mutable parameter.
    2. * (T *) (AP-_bnd (T,_AUPBND))--then after restoring the AP, convert it to a "T" type pointer and seek the value of the pointer.

Now you can see that this macro means to ask for the value that the current AP points to and point the AP to the next target.

4. Va_end (VL) Clear the VL pointer to null

#define VA_END (AP) (void) 0

Note: This code only compiles and runs under Windows to output the correct results, and under Linux, int Sum (int first, int second, ...) is required. change to int Sum (int first, ...), i.e. delete int second, because if second is written out, it is not a mutable parameter, and first is not a mutable parameter. The modified function works under Linux and under Windows.

After figuring out the principle of the example code above, we can implement such a function ourselves manually.

#include <stdio.h>

int Sum(int first, int second,...)

{

int sum = 0, t = first;

Char * VL; Define a pointer

VL = (char *) &first;//the pointer to the first argument

while (*VL! =-1)//-1 is a pre-given Terminator

{

Sum + = * (int *) vl;//type conversion

VL + = sizeof (int);//move the pointer so that the pointer points to the next parameter

}

return sum;

}

int main (int argc, char* argv[])

{

printf ("The sum is%d\n", SUM (30, 20, 10,-1));//-1 is the parameter end flag

return 0;

}

There are two ways to actually declare a mutable parameter: the first one is recommended.

The first: the prototype declaration of a function with a variable number of parameters, including the header file Stdarg.h, in ANSI standard form:

Type funcname (Type para1, type Para2, ...)

The second: Contains the header file Varargs.h, in the case of a UNIX System V-compatible declaration method, the variable number of function prototypes are:

Type FuncName (va_alist)

Va_dcl

VA_DCL is a macro, the macro defines a prototype that already contains a semicolon, so you don't need to add a semicolon. VA_DCL is a detailed statement of the va_alist. VA_DCL in the code must be given, va_alist in the VC can be given as is, or can be omitted, but on Unix on the CC or Linux on the GCC should be omitted.

on the transfer problem of variable parameters

Some people ask this question, if I define a variadic function, and call other variadic functions inside this function, then how to pass the parameters? The above example is to use the macro va_arg to extract the parameters to use, can not extract, directly pass them to another function?

Let's look at the implementation of printf first:

int __cdecl printf (const char *format, ...)
{
Va_list arglist;
int buffing;
int retval;

Va_start (arglist, format); ArgList point to the first parameter following format

...//Don't care about other code
retval = _output (stdout,format,arglist); Passing format formats and parameters to the output function

...//Don't care about other code
return (retval);

}

Let's start by imitating this function to write a:

#include <stdio.h>
#include <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 it and see that there are a few mistakes. Careful analysis of the reason, according to the definition of the macro we know that arglist is a pointer to the first variable parameter, but all the parameters are in the stack, so arglist points to a position in the stack, through the value of arglist, we can directly see the contents of the stack:

Inside the arglist, the contents include

0067fd78 E0 FD 67 00//Pointer to the string "This is a test"

0067fd7c 0A 00 00 00//Integer I value

0067fd80 14 00 00 00//Integer J value

0067fd84 3D 0A D7//double variable F, occupied 8 bytes

0067fd88 A3 B0 28 40

0067fd8c 00 00 00 00

If you call printf directly (FMT, arglist), simply 0067fd78 the value of the arglist pointer into the stack and then put the format string into the stack, which is equivalent to calling:

printf (FMT, 0067fd78);

Naturally, a call like this will definitely cause an error.

Can we extract the parameters one by one and pass them on to the other functions? Consider the problem of passing all the parameters in one go.

This is not possible if the system library function is called. Because the extraction parameter is in the run state , and the parameter is in the stack is determined at compile time . It is not possible for the compiler to predict the running state to give the correct parameters in the stack code. While we can extract each parameter in the running state, it is not possible to stack the parameters all at once , even if it is difficult to use the assembly code, because it is not only a simple push code can be achieved.

---------------------------------------------------------

Question one:

The above code is tested to output normally. In other words, we implement a variable function by using pointers. But here's another problem: all the arguments to the SUM function are of type int, and we know beforehand that we want to move the sizeof (int) bit, but what if the parameter types are different?

Answer and analysis: This is really a troublesome problem, because the different data types occupy the number of bytes may be not the same (such as a double type of 8 characters, short int is 2), it is difficult to determine in advance how many bytes should be moved! However, there is still some way, this is the use of pointers, regardless of the type of pointer, is occupied 4 bytes, so, can be all passed as parameters are set as pointers, so that you can move a fixed 4 bytes to achieve the purpose of traversing variable parameters, as to how to get the contents of the pointer and use them, Of course, it is impossible to know beforehand. So this is probably the reason why a function like printf (), scanf () also needs a format control, ^_^! But there are still a lot of problems to be implemented, so let's steal vprintf () to implement a function like the printf () function, the code is as follows:

void myprint(const char *frm, ...)

{

Va_list VL;

Va_start(vl, frm);

vprintf(frm, vl);

Va_end(vl);

}  

-----------------------------------------------------------

question two: There is another problem, the variant of the above problem, but the same meaning: there is no way to write a function, the specific form of the function parameter can be determined at run time?

Answer and Analysis: There is no "formal" solution, but there is one, because there is a function has given us a role model, that is main (), its prototype is:
int main (int argc,char *argv[]);
The parameters of the function are argc and argv.
Think about it, "you can only determine the parameter form at run time," which means that you can't see the parameters you accept from the declaration, which means there is no fixed form of parameters at all. The common approach is that you can define a void * type parameter, use it to point to the actual parameter area, and then arbitrarily interpret the meanings of the functions according to their needs. This is the meaning of the argv in the main function, and the ARGC is used to indicate the actual number of arguments, which provides further convenience for our use, and of course, this parameter is not required.
Although the parameters do not have a fixed form, but we must be in the function to parse the meaning of the parameters, so, of course, there will be a requirement, that is, the caller and the callee to the parameters of the format of the content, size, validity and so on all aspects of the agreement, otherwise the opposite of the words will be miserable.

-------------------------------------------------------------
Question three: the transfer of variable-length parameters
Sometimes, you need to write a function to pass its variable length parameters directly to another function, can I ask if this requirement is implemented?
Answer and Analysis: At present, you have no way to do this directly, but we can detour forward, first, we define the parameters of the called function is the va_list type, while in the calling function to convert the variable-length parameter list to va_list, so that the variable-length parameters can be passed. See below:

void Subfunc (char *fmt, va_list ARGP)
{
...
arg = Va_arg (FMT, ARGP); /* Remove the required parameters from the ARGP */
...
}
void MainFunc (char *fmt, ...)
{
Va_list ARGP;
Va_start (ARGP, FMT); /* Convert variable-length parameter to Va_list */
Subfunc (FMT, ARGP); /* Pass va_list to the child function */
Va_end (ARGP);
...
}

-------------------------------------------------------------

Question four: How to discriminate parameter types of variable parameter functions?
The function form is as follows:
void Fun (char* str,...)
{
......
}
If the number of parameters is greater than 1, how to determine the parameter type of the 2nd after the parameter??? It is best to have source code description!
Answer and Analysis: Can't judge. The implementation of variable parameters is mainly implemented by three macros: Va_start,va_arg,va_end.
As mentioned upstairs, for example printf ("%d%c%s", ....) is to determine the type of the subsequent parameter by%d,%c,%s in the format string, but you can also refer to this method to determine the type of the indeterminate parameter.

-------------------------------------------------------------
Question five: Define a limit for variable-length parameters
Why doesn't my compiler allow me to define functions like variable-length parameters, but not any fixed parameters?
int f (...)
{
...
}

Answer and Analysis: No. This is required by ANSI C, you must define at least one fixed parameter.
This parameter is passed to Va_start (), and then Va_arg () and Va_end () are used to determine the type and value of the variable-length parameter for all actual calls.

---------------------------------------------------------------------
question six: Obtaining variable-length parameters
There is a function with a variable-length parameter, in which the following code is used to obtain an argument of type float:
Va_arg (ARGP, float);

Can you do that?
Answers and Analysis:not to. In variable-length parameters, the "widening" principle is applied. That is, the float type is expanded to Double;char, and short is expanded into int. Therefore, if you want to go to a variable-length parameter list that was originally a float type parameter, you need to use Va_arg (ARGP, double). For char and short types, use Va_arg (ARGP, int).
---------------------------------------------------------------------
question seven: variable-length parameter type is a function pointer
I want to use Va_arg to extract parameters with variable length parameters that are typed as function pointers, but the result is always incorrect.
Answer and analysis: This is related to the realization of va_arg. A simple, demo version of the Va_arg implementation is as follows:
#define VA_ARG (ARGP, type) (* (Type *) (((ARGP) + sizeof (type))-sizeof (type))
Where the type of ARGP is char *.
If you want to use Va_arg to extract parameters of the function pointer type from the mutable argument list, for example
Int (*) (), then Va_arg (ARGP, Int (*) ()) is extended to:
(* (Int (*) () *) (((ARGP) = sizeof (int (*) ()))-sizeof (int (*) ())))
Obviously, (Int (*) () *) is meaningless.
The solution to this problem is to define the function pointer with a typedef as a separate data type, for example:
typedef int (*FUNCPTR) ();
The call to Va_arg (ARGP, FUNCPTR) will be extended to:
(* (* (FUNCPTR *) (((ARGP) + + sizeof (FUNCPTR))-sizeof (FUNCPTR))
This can be done by compiling the check.

Knowledge expansion

As you may have guessed, what do I expand to extend?! ^_^

Two types of function calling conventions are briefly introduced

__stdcall (c + + default)

    1. Parameters are pressed from right to left into the stack
    2. function is called by the caller to modify the stack
    3. The function name (at the compiler level) automatically adds a leading underscore followed by an @ symbol followed by the size of the parameter

__CDECL (c language Default)

    1. Parameters are pressed from right to left into the stack
    2. The parameters are clear by the caller, manually cleared, the called function does not require the caller to pass many arguments, the caller passes too many or too few arguments, and even a completely different parameter does not produce a compile-time error.

Then, the argument function is called (and can only be): __cdecl.

Deep Exploration of variable length parameters of C-language functions

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.