Abstract: printf seems to be one of the most powerful functions in the C language library, not only because it can format the output, but also because it has no limit on the number of parameters, if you need a few, you can give them a few. Printf is a powerful adaptability to the number and type of parameters, which leads to a strong interest in its exploration.
Keyword: printf, Variable Parameter
1. Usage
int a =10;double b = 20.0;char *str = "Hello world";printf("begin print/n");printf("a=%d, b=%.3f, str=%s/n", a, b, str);...
From the usage of printf, it is not difficult to find a rule that the first parameter of printf is always a string regardless of the number of its variable parameters. This is the first parameter, so that it can confirm the number of parameters that follow. The size of stack space occupied by each trailing parameter is determined by the first format string. However, the following code shows how printf obtains the parameter value after the first parameter.
2. Implementation of the printf function
//acenv.htypedef char *va_list;#define _AUPBND (sizeof (acpi_native_int) - 1)#define _ADNBND (sizeof (acpi_native_int) - 1) #define _bnd(X, bnd) (((sizeof (X)) + (bnd)) & (~(bnd)))#define va_arg(ap, T) (*(T *)(((ap) += (_bnd (T, _AUPBND))) - (_bnd (T,_ADNBND))))#define va_end(ap) (void) 0#define va_start(ap, A) (void) ((ap) = (((char *) &(A)) + (_bnd (A,_AUPBND))))//start.cstatic char sprint_buf[1024];int printf(char *fmt, ...){va_list args;int n;va_start(args, fmt);n = vsprintf(sprint_buf, fmt, args);va_end(args);write(stdout, sprint_buf, n);return n;}//unistd.hstatic inline long write(int fd, const char *buf, off_t count){return sys_write(fd, buf, count);}
3. Analysis
From the code above, printf does not seem complicated. It uses a macro va_start to put all variable parameters into a memory directed by ARGs, and then calls vsprintf. the real number of parameters and format confirmation are done in vsprintf. Because the vsprintf code is complex and is not the focus of our discussion here, we will not list it below. The focus of our discussion here is the implementation of the va_start (AP, a) macro, which is of great guidance significance for locating the parameters following parameter. Now, set # define va_start (AP, A) (void) (AP) = (char *) & (A) + (_ BND (, _ aupbnd:
Va_start (AP, A) {char * ap = (char *) (& A) + sizeof (A) and INT type size and address alignment}
In va_start (ARGs, FMT) of printf, the FMT type is char *. Therefore, if a 32 is system sizeof (char *) = 4, if the int size is also 32, then va_start (ARGs, FMT); equivalent to char * ARGs = (char *) (& FMT) + 4; the value of argS is exactly the address of the first parameter after FMT. For the following Variable Parameter Functions
void fun(double d,...) { va_list args; int n; va_start(args, d); }
Then va_start (ARGs, d); equivalent
char *args = (char *)&d + sizeof(double);
In this case, argS points to the first parameter after D.
The implementation of variable parameter functions is related to the stack structure of function calls. Under normal circumstances, the function parameter inbound rule of C/C ++ is _ stdcall, which is from right to left, that is, the rightmost parameter in the function is first written into the stack. For functions
void fun(int a, int b, int c) { int d; ... }
Its stack structure is
0x1ffc-->d 0x2000-->a 0x2004-->b 0x2008-->c
For any compiler, the size of each stack unit is sizeof (INT), and each parameter of the function must occupy at least one treasure unit, such as void fun1 (char a, int B, double C, short D) the stack structure of a 32 system is
0x1ffc --> A (4 bytes) 0x2000 --> B (4 bytes) 0x2004 --> C (8 bytes) 0x200c --> D (4 bytes)
For functions void fun1 (char a, int B, double C, short D)
If you know the address of parameter A, you can use the address of a to calculate the address of the parameter after a and then take the corresponding value, the number of subsequent parameters can be directly specified by variable A. Of course, the number and type of subsequent parameters can be determined based on the % pattern number in the first parameter, just like printf. If the number of parameters is directly determined by the first parameter A, if the type of the subsequent parameters does not change and is known, we can take the subsequent parameters in this way, it is assumed that all subsequent parameter types are double;
void fun1(int num, ...){ double *p = (double *)((&num)+1); double Param1 = *p; double Param2 = *(p+1); ... double Paramn *(p+num);}
If the type of subsequent parameters changes and is unknown, you must set the mode in a parameter to match the number and type of subsequent parameters, just like printf, of course, we can define our own mode. For example, we can use I to represent the int parameter, and D to represent the double parameter. For simplicity, we use one character to represent a parameter, the parameter type is determined by the name of the character, and the order in which the characters appear also indicates the order of subsequent parameters. In this way, we can define a ing table for character and parameter types,
i---ints---signed shortl---longc---char
The "ILD" mode is used to indicate that there are three subsequent parameters in the order of int, long, and double parameters. Then, we can define our own version of printf as follows:
void printf(char *fmt, ...){ char s[80] = ""; int paramCount = strlen(fmt); write(stdout, "paramCount = " , strlen(paramCount = )); itoa(paramCount,s,10); write(stdout, s, strlen(s)); char *p = (char *)(&fmt) + sizeof(char *); int *pi = (int *)p; for (int i=0; i<paramCount; i++) { char line[80] = ""; strcpy(line, "param"); itoa(i+1, s, 10); strcat(line, s); strcat(line, "="); switch(fmt[i]) { case 'i': case 's': itoa((*pi),s,10); strcat(line, s); pi++; break; case 'c': { int len = strlen(line); line[len] = (char)(*pi); line[len+1] = '/0'; } break; case 'l': ltoa((*(long *)pi),s,10); strcat(line, s); pi++; break; default: break; } }}
You can also define our Max function, which returns the maximum value of Multiple Input integer parameters.
int Max(int n, ...){ int *p = &n + 1; int ret = *p; for (int i=0; i<n; i++) { if (ret < *(p + i)) ret = *(p + i); } return ret;}
It can be called as follows. The number of subsequent parameters is specified by the first parameter.
int m = Max(3, 45, 12, 56);int m = Max(1, 3);int m = Max(2, 23, 45);int first = 34, second = 45, third=5;int m = Max(5, first, second, third, 100, 4);
Conclusion
Note that the actual number of variable parameters must be greater than or equal to the number specified in the previous mode, that is, it does not matter if there are more parameters, but it cannot be less. If it is missing, it will access the stack area other than the function parameters, which may cause the program to collapse. If the type in the preceding mode does not match the type of the actual parameter, the program may crash. This happens if the data length specified in the mode is greater than the subsequent parameter length. For example:
printf("%.3f, %.3f, %.6e", 1, 2, 3, 4);
The default type of the parameter 1, 2, 3, 4 is integer, and the Data Length specified by the mode must be double. The data length is larger than that of the int type. In this case, it is possible to access a region other than the function parameter stack, this causes danger. But printf ("% d, % d, % d", 1.0, 20., 3.0); although the results may be incorrect, it does not cause catastrophic consequences. Because the actual length of the specified parameter is longer than the required parameter length, the stack will not cross-border.