In the process of learning C language, we may rarely write variable parameter function, the impression that the university teachers do not seem to have mentioned, but I found that the variable parameter function is very clever, so it is specifically to analyze the implementation of the variable parameter function principle. Without the support of standard C, we write our own code to implement it.
Let's take a look at an implementation code:
#include <stdio.h>
#define Va_list void*
#define VA_ARG (ARG, type) * (type*) arg; arg = (char*) arg + sizeof (type);
#define VA_START (ARG, start) arg = (va_list) ((char*) & (start) + sizeof (start))
int sum (int nr, ...)
{
int i = 0;
int result = 0;
va_list arg = NULL;
Va_start (ARG, NR);
for (i = 0; i < nr; i++)
{
result = Va_arg (arg, int);
}
return result;
}
int main (int argc, char* argv[])
{
printf ("%d\n", SUM (4, 100,100,100,100));
printf ("%d\n", SUM (3, 200, 200, 200));
return 0;
}
The results of the operation are as follows:
#define Va_list void* Through this code we implemented the definition va_list is a pointer, the parameter type is indefinite, it can point to any type of pointer. In order for Arg to point to the first variable parameter, we use the address of NR with the data type size of NR, which can be achieved by using the following definition.
#define VA_START (ARG, start) arg = (va_list) ((char*) & (start) + sizeof (start)).
By ((char*) & (start) + sizeof (start), you can get the address of the first variable parameter and cast it to the va_list type.
After the first variable parameter is successfully removed, the next task is to continue to remove the variable parameter, as in the case of the first variable parameter above, with arg = (char*) arg + sizeof (type), to enable Arg to point to the next mutable parameter, type to the variable parameter, In this way, the variable parameters can be taken out.
Here, by the way, give the assembly code of the above implementation code, interested to read and deepen the reading ability of the underlying assembly code.
. File "VARARGS.C"
. text
. Globl Sum
. Type SUM, @function
Sum
PUSHL%EBP
MOVL%esp,%EBP
Subl $16,%esp
MOVL $ -4 (%EBP)
MOVL $ -8 (%EBP)
MOVL $ -12 (%EBP)
Leal (%EBP),%eax
Movl%eax, -12 (%EBP)
MOVL $ -4 (%EBP)
JMP. L2
. L3:
Movl-12 (%EBP),%eax
MOVL (%eax),%eax
Addl%eax, -8 (%EBP)
Addl $ -12 (%EBP)
Addl $ -4 (%EBP)
. L2:
MOVL 8 (%EBP),%eax
Cmpl%eax, -4 (%EBP)
JL. L3
Movl-8 (%EBP),%eax
Leave
Ret
. Size sum,.-sum
. section. Rodata
. LC0:
. String "%d\n"
. text
. GLOBL Main
. type Main, @function
Main
PUSHL%EBP
MOVL%esp,%EBP
Andl $-16,%esp
Subl $32,%esp
MOVL $ (%ESP)
MOVL $ (%ESP)
MOVL $8 (%ESP)
MOVL $4 (%ESP)
MOVL $, (%ESP)
Call sum
MOVL $. LC0,%edx
MOVL%eax, 4 (%ESP)
Movl%edx, (%ESP)
Call printf
MOVL (%ESP)
MOVL, 8 (%ESP)
MOVL, 4 (%ESP)
MOVL $, (%ESP)
Call sum
MOVL $. LC0,%edx
MOVL%eax, 4 (%ESP)
Movl%edx, (%ESP)
Call printf
MOVL $,%eax
Leave
Ret
. size main,.-main
. Ident "GCC: (Ubuntu/linaro 4.5.2-8ubuntu4) 4.5.2"
. section. Note. Gnu-stack, "", @progbits
Several of these instructions are explained here.
The operation of the leave instruction is equivalent to the following two instructions:
MOVL%EBP,%esp
POPL%EBP
The operation of a RET instruction is equivalent to the following instruction:
Pop%eip
If you do not understand the At&t assembly grammar rules, you can look at the article I wrote earlier.
So far it's time to say it's over, but the attentive reader may have found a problem, which is that we have a red-flagged code in the first part of the code, as follows:
#define VA_START (ARG, start) arg = (va_list) ((char*) & (start) + sizeof (start))
Why do you have to explain this code alone, there must be a reason, because ((char*) & (Start) +sizeof (start) This code is specific to the use of (char*) for the cast, why not use (int*) Make a cast, such as the following code:
#include <stdio.h>
#include <stdlib.h>
#define Va_list void*
#define VA_ARG (ARG, type) * (type*) arg; arg = (char*) arg + sizeof (type);
#define VA_START (ARG, start) arg = (va_list) ((int*) & (start) + sizeof (START))//modified to (int*)
int sum (int nr, ...)
{
int i = 0;
int result = 0;
va_list arg = NULL;
Va_start (ARG, NR);
for (i = 0; i < nr; i++)
{
result = Va_arg (arg, int);
}
return result;
}
int main (int argc, char* argv[])
{
printf ("%d\n", SUM (4, 100,100,100,100));
printf ("%d\n", SUM (3, 200, 200, 200));
return 0;
}
The results of the operation are:
Obviously the results of the operation is wrong, why there is such a mistake, we do not analyze, first of all to see what we do next changes:
#include <stdio.h>
#include <stdlib.h>
#define Va_list void*
#define VA_ARG (ARG, type) * (type*) arg; arg = (char*) arg + sizeof (type);
#define VA_START (ARG, start) arg = (va_list) ((int*) & (start) + sizeof (START)/4)//Note the change of the red part of the contrast
int sum (int nr, ...)
{
int i = 0;
int result = 0;
va_list arg = NULL;
Va_start (ARG, NR);
for (i = 0; i < nr; i++)
{
result = Va_arg (arg, int);
}
return result;
}
int main (int argc, char* argv[])
{
printf ("%d\n", SUM (4, 100,100,100,100));
printf ("%d\n", SUM (3, 200, 200, 200));
return 0;
}
The results of the operation are as follows:
The running result is correct.
Now to analyze why these two results appear, look at the following I give the diagram and the code should be able to clearly understand why there are the above two kinds of running results.
The code is as follows:
#include <stdio.h>
int main ()
{
int a = 12;
int *p_int = &a;
Char *p_char = (char*) &a;
printf ("%d \ T", sizeof (char));
printf ("%d \ T", sizeof (int));
printf ("%d \ T", p_int+1);
printf ("%d \ T", p_char+1);
return 0;
}
The results of the operation are:
Modify the code in the red section above to get the following code:
#include <stdio.h>
int main ()
{
int a = 12;
int *p_int = &a;
Char *p_char = (char*) &a;
printf ("%d \ T", sizeof (char));
printf ("%d \ T", sizeof (int));
printf ("%d \ T", p_int+1);
printf ("%d \ T", p_char+4);
return 0;
}
Note the changes before and after the code ...
The results of the operation are as follows:
First look at the given diagram, the element that the int pointer points to occupies four bytes of space, and the cell that the char pointer points to occupies only one byte of space. So if the plastic pointer wants to take off an argument, just add 1, but if it's a char pointer, and if the current argument is int, then you need to add 4 to get a parameter. It is worth noting, however, that the storage units for both int* and char* are 4 bytes, as determined by the 32-bit computers we use. In order to deepen the impression, we specifically give the following code:
#include <stdio.h>
int main ()
{
int a = 12;
int *p_int = &a;
Char *p_char = (char*) &a;
printf ("%d \ T", sizeof (char*));
printf ("%d \ T", sizeof (int*));
return 0;
}
The results of the operation are as follows:
This is really the end of it, the total can not go on forever, hehe ...
A lot of code is just modified a little, I have posted a complete code, is to hope that you can read the process directly copy the past, look at the operation effect, deepen the impression. Or that sentence, C language is broad and profound, I am still a C-language rookie, the above content inevitably wrong. If you want to reprint, please specify the source.