The realization principle of variable parameter function in C language

Source: Internet
Author: User
Tags variadic

The implementation of the VARIADIC function is closely related to the stack structure of the function call, and normally the function parameter of C is __stdcall, which is from right to left, that is, the rightmost parameter in the function is first entered into the stack. For example, for a function:

  void Fun (int A, int b, int c)  {        int D;        ...  }

Its stack structure is

0x1ffc-->d

0x2000-->a

0x2004-->b

0x2008-->c

For most compilers on 32-bit systems, the size of each stack unit is sizeof (int), and each parameter of the function must be at least one stack unit size, such as the function void Fun1 (char A, int B, double C, short D) The structure of the stack for a 32 system is

0x1ffc-->a (4 bytes) (for word alignment)

0x2000-->b (4 bytes)

0x2004-->c (8 bytes)

0x200c-->d (4 bytes)

Therefore, all parameters of a function are stored in a linear contiguous stack space, based on this storage structure, so that the first common parameter that is required in a variadic function is used to address the type of all subsequent mutable parameters and their values.

First look at the fixed parameter list function:

void Fixed_args_func (int A, double b, char *c) {        printf ("a = 0x%p\n", &a);        printf ("B = 0x%p\n", &b);        printf ("c = 0x%p\n", &c);}

For a function with a fixed argument list, the names and types of each parameter are directly visible, and their addresses can be directly obtained, for example: by &a We can get the address of a and learn from the function prototype declaration that A is of type int.

But for the function of variable length parameters, we are not so smooth. Fortunately, according to the C standard, a function that supports variable-length parameters must have at least one left-most fixed parameter in the prototype declaration (this differs from traditional C, which allows a pure variable-length parametric function without any fixed parameters ), This way we can get the address of the fixed parameter, but still cannot get the address of the other variable length parameter from the declaration, for example:

void Var_args_func (const char * fmt, ...) {    ... ... }

Here we can only get the address of the FMT this fixed parameter, only from the function prototype we are unable to determine "..." There are several parameters, parameters are what type. Recall the process of function transfer, no matter "..." in the number of parameters, what type of each parameter, they are the same as the parameters of the parameter process is the same, simply is the stack operation, and stack this thing is open to us. Thus, once we know the position of a fixed parameter on the stack of a function frame, it is entirely possible to deduce the position of other variable-length parameters.

Let's first use the Fixed_args_func function above to determine the stacking order.

int main () {    fixed_args_func (n, 5.40, "Hello World");    return 0;} A = 0x0022ff50b = 0x0022ff54c = 0x0022ff5c

From this point of view, it is clear that the parameters are from right to left, each pressed into the stack (the extension of the stack is from high address to low address, the bottom of the stack occupy the highest memory address, first into the stack parameters, its location is the highest ).

We can basically draw a conclusion that:

C.ADDR = b.addr + x_sizeof (b);  /* Note:  x_sizeof!=sizeof */b.addr = a.addr + x_sizeof (a);

With the "equation" above, it seems that we can deduce the position of the variable parameter in the void Var_args_func (const char * fmt, ...) function.  At least the position of the first variable parameter should be: first_vararg.addr = fmt.addr + x_sizeof (FMT); Based on this conclusion, we try to implement a function that supports mutable parameters:

#include <stdarg.h>
#include <stdio.h>

void Var_args_func (const char * fmt, ...) { char *ap; AP = ((char*) &fmt) + sizeof (FMT); printf ("%d\n", * (int*) AP); AP = ap + sizeof (int); printf ("%d\n", * (int*) AP); AP = ap + sizeof (int); printf ("%s\n", * ((char**) AP));} int main () { Var_args_func ("%d%d%s\n", 4, 5, "Hello World");
return 0;
}

Expected output:
4
5
Hello World

Let's explain this procedure first. We use the AP to get the address of the first argument, we know that the first parameter is 4, an int, so we use the (int*) AP to tell the compiler that the memory of the AP-headed address we want to use as an integer, the * (int*) AP Gets the value of the parameter; the next parameter is 5, Another int, the address is AP + sizeof (the first parameter), that is, AP + sizeof (int), also we use the * (int*) AP to obtain the value of the parameter; the last parameter is a string, which is char*, unlike the first two int parameters, After AP + sizeof (int), the AP points to the first address of the stack on the char* type of memory (we'll call it tmp_ptr, char *tmp_ptr), AP-and &tmp_ptr, and we're not going to output printf ("%s \ n ", AP), but printf ("%s\n ", tmp_ptr); printf ("%s\n", AP) is intended to output a block of memory referred to by the AP as a string, but the 4 bytes occupied by AP-and &tmp_ptr,tmp_ptr are obviously not strings, but rather an address. How to make &tmp_ptr is a char * * type, we will make the AP cast (char**) AP <=> &tmp_ptr so that our access to tmp_ptr only needs to precede (char**) the AP with a *, i.e. printf ("%s\n", * (char**) AP);

Everything seems to be perfect, the compilation is also very smooth through, but after running the above code, not only not get the expected results, but the entire compiler will be forced to close (you can try to run it), the original AP pointer in the later did not follow the expected requirements of the second variable parameter, that does not point to 5 where the first address, Instead, it points to an unknown area of memory, so the compiler forcibly shuts it down. In fact, the error begins with: AP = ap + sizeof (int), because of memory alignment, when the compiler presses the parameter on the stack, not one next to the other, the compiler will place it on the type-aligned address according to the type of the argument, so there may actually be gaps between the arguments on the stack. (C Language Memory alignment detailed (1) C Language Memory alignment detailed (2) C Language Memory alignment explained (3)) so at this point the AP calculation should be changed to: AP = (char *) AP +sizeof (int) + __va_rounded_size (int);

The corrected code is as follows:

#include <stdio.h> #define __VA_ROUNDED_SIZE (Type)    ((sizeof (TYPE) + sizeof (int)-1)/sizeof (int)) * sizeof ( int)) void Var_args_func (const char * fmt, ...) {    char *ap;    AP = ((char*) &fmt) + sizeof (FMT);    printf ("%d\n", * (int*) AP);              AP = (char *) AP + sizeof (int) + __va_rounded_size (int);    printf ("%d\n", * (int*) AP);    AP = ap + sizeof (int) + __va_rounded_size (int);    printf ("%s\n", * ((char**) AP));} int main () {    Var_args_func ("%d%d%s\n", 4, 5, "Hello World");    return 0;}

Var_args_func just to demonstrate that the number and type of arguments are not judged based on the format string in the FMT message, but rather that they are written directly in the implementation.

To accommodate the portability of the code, the C standard library provides a number of advantages in stdarg.h for the variable length parameter. Here is a simple example of how the standard library can be used to support variable-length parameters:

1 #include <stdarg.h> #include <stdio.h> 2  3 void std_vararg_func (const char *FMT, ...) {4         va_list APs; 5         Va_start (AP, FMT), 6  7         printf ("%d\n", Va_arg (AP, Int.)), 8         printf ("%f\n", Va_arg ( AP, double)); 9         printf ("%s\n", Va_arg (AP, char*)),         Va_end (AP),}13 Int main () {         std_vararg_func ("%d%f%s\n", 4 , 5.4, "Hello World");        return 0;}

Compared to the implementation of Std_vararg_func and Var_args_func,va_list seems to be char*, Va_start seems to be ((char*) &fmt) + sizeof (FMT),Va_arg seems to be the first address to get the next parameter. Yes, most of the platforms under stdarg.h va_list, Va_start and Var_arg implementations are similar. General Stdarg.h will contain a lot of macros that look more complex.

Let's explore how to write a simple variable-parameter C function.

Using mutable parameters should have the following steps:
1) First define a variable of type va_list in the function, here is ARG_PTR, this variable is a pointer to the parameter.
2) Then use the Va_start macro to initialize the variable arg_ptr, the second parameter of the macro is the first variable parameter of the previous parameter, is a fixed parameter.
3) Then return the variable parameters with Va_arg and assign the value to the integer J. The second parameter of Va_arg is the type of the parameter you want to return, and this is the int type.
4) Finally, the Va_end macro is used to end the variable parameter acquisition. Then you can use the second parameter in the function. If the function has more than one variable parameter, call Va_arg to get each parameter.

In "C programming language", Ritchie provides a simple printf function:

1 #include <stdarg.h> 2  3 void minprintf (char *fmt, ...) 4 {5     va_list ap; 6     Char *p, *sval; 7     Int IV Al 8     double dval; 9     Va_start (AP, FMT), one for     (P = fmt; *p; p++) {         if (*p! = '% ') {Putchar             (*p); 14
   continue;15         }16         switch (*++p) {         $ case ' d ':             ival = Va_arg (AP, int),             printf ("%d", ival);             break;21 case         ' F ':             dval = Va_arg (AP, double);             printf ("%f", dval);             break;25 Case         ' s ': $             for (Sval = Va_arg (AP, char *); *sval; sval++)                 Putchar (*sval);             break;29         Default:30             Putchar (*p);             break;32         }33}34     va_end (AP); 35}

The realization principle of variable parameter function in C language

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.