Variable Parameter parameters are often used in programming. You can query some materials, sort them out, and finally write a simple macro-defined variable parameter LOG. This instance can be used frequently. Variable Parameter definition we use the printf () function most often when learning C language, but we seldom know its prototype. In fact, the printf () parameter is a variable parameter. Think about it, we can use it to print various types of data. Let's take a look at its prototype: intprintf (const char * format ,...); its first parameter is format, which is a fixed parameter. The number and type of parameters following it are variable (with three vertices "…" Parameters placeholder), which can be called in the following format: printf ("% d", I); printf ("% s", s ); printf ("thenumber is % d, string is: % s", I, s ); example ① A simple variable parameter C function has at least one integer parameter in the simple_va_fun parameter list, followed by a placeholder... The number of subsequent parameters is not fixed .. In this example, all input parameters must be integers. The function only prints the values of all parameters. # Include <stdio. h> # include <stdarg. h> voidsimple_va_fun (int start ,...) {va_listarg_ptr; intnArgValue = start; intnArgCout = 0; // The number of variable parameters va_start (arg_ptr, start ); // determine the memory start address of the Variable Parameter Based on the fixed parameter address. Do {++ nArgCout; printf ("the % d th arg: % d \ n", nArgCout, nArgValue); // output the values of each parameter nArgValue = va_arg (arg_ptr, int); // get the value of the next variable parameter} while (nArgValue! =-1); return;} intmain (int argc, char * argv []) {simple_va_fun (100,-1); simple_va_fun (100,200,-1); return0 ;} ② format a FILE stream, which can be used for the log FILE * logfile; intWriteLog (const char * format ,...) {va_listarg_ptr; va_start (arg_ptr, format); intnWrittenBytes = vfprintf (logfile, format, arg_ptr); va_end (arg_ptr); returnnWrittenBytes;} can be seen from the implementation of this function, the following steps should be taken to use variable parameters: (1) these macros are used in the program: voidva_start (va_list arg_ptr, prev_param); typeva_arg (Va_list arg_ptr, type); voidva_end (va_list arg_ptr); va indicates variable-argument (variable Parameter. these macros are defined in stdarg. h, so the program that uses variable parameters should contain this header file. (2. the function first defines a va_list variable, which is arg_ptr. This variable is a pointer to store the parameter address. the parameter value can be obtained only after obtaining the parameter address and combining the parameter type. (3. then, use the va_start macro to initialize the variable arg_ptr defined in (2). The second parameter of this macro is the previous parameter in the variable parameter list, that is, the last fixed parameter. (4. then, use the va_arg macro to make arg_ptr return the address of the Variable Parameter. After obtaining the address, you can obtain the value of the parameter based on the parameter type. Condition. Set the end condition ① To determine whether the parameter value is-1. Note that the called function does not know the correct number of variable parameters when calling. The programmer must specify the end condition in the code. ② Call the macro va_end. Analyze the va _ * macro definition. We already know that va_start, va_arg, and va_end are in stdarg. h is defined as a macro, because the hardware platform and the compiler are different, so the definition of the macro is also different. Stdarg in the following VC ++ 6.0. code in h (the file path is \ vc98 \ include \ stdarg under the VC installation directory. h) typedefchar * va_list; # define_INTSIZEOF (n) (sizeof (n) + sizeof (int)-1 )&~ (Sizeof (int)-1) # defineva_start (ap, v) (ap = (va_list) & v ++ _ INTSIZEOF (v) # defineva_arg (ap, t) (* (t *) (ap + = _ INTSIZEOF (t)-_ INTSIZEOF (t) # defineva_end (ap) (ap = (va_list) 0) in linux, define typedefchar * va_list; # define _ va_rounded_size (TYPE) (sizeof (TYPE) + sizeof (int)-1)/sizeof (int )) * sizeof (int) # defineva_start (AP, LASTARG) (AP = (char *) & (LASTARG) + _ va_rounded_size (LASTARG) voidva_e Nd (va_list); # defineva_end (AP) (AP = (char *) 0) # defineva_arg (AP, TYPE) (AP + =__ va_rounded_size (TYPE ), \ * (TYPE *) (AP-_ va_rounded_size (TYPE) to understand the meanings of these macro definitions, you must first understand: 1. stack direction and parameter inbound sequence 2. CPU alignment 3. memory Address expression. 1. the stack is based on Intel 32-bit CPU analysis. The stack growth direction is downward, that is, the stack is at the high address while the stack is at the low address; from the bottom of the stack to the top of the stack, the address is from high address to low address, because it is called downward growth ,. We can see the growth direction of the stack from the two figures above before and after the stack is pressed. In the Intel32-bit CPU, mongown or linux both use its protection mode, the ss specifies all the blocks in the stack. The ebp points to the stack base address, and the esp points to the stack top. Obviously, after the push command is executed, the esp value is reduced by 4, and after pop, the esp value increases by 4. The size of the storage space of each element in the stack determines the increase or decrease of the esp value after the push or pop command. The size of the stack element in the Intel32-bit CPU is 16-bit or 32-bit, which is defined when the stack segment is defined. In Windows and Linux systems, the kernel code has defined the size of the stack element as 32 characters, that is, a sizeof (int )). Therefore, the size of the user space's stack element must be 32 bits, so that the address of each stack element is aligned to 4 bytes. The function call conventions in C language are very important for writing variable parameter functions. Only when they are clear can the program be controlled more easily. In advanced programming languages, the function call conventions include stdcall, cdecl, fastcall, thiscal, and naked call. Cdel is a standard call convention in C language. If the call convention is not specified in the definition function (you can add the convention name before the function name), the compiler considers it a cdel convention, from the above several conventions, only cdel conventions can define variable parameter functions. The following are important features of the cdel Convention: If function A calls function B, function A is called the caller, and function B is called the callee ). Caller stores the parameters passed to callee in the stack, and the pressure stack order is in the order from right to left in the parameter list; callee is not responsible for clearing the stack, but is cleared by caller. We will use a simple example to illustrate the problem and use the Nasm Assembly format to write the corresponding assembly code. The program segment is as follows: voidcallee (int a, int B) {int c = 0; c = a + B;} voidcaller () {callee () ;}to analyze what happened during the call. When the program execution point comes to caller, it will call the callee function. before jumping to the callee function, it will first press the passed parameters to the stack, and then in the order of right to left, that is, the commands translated into assembly are push 2; push 1; and figure 2 (a) in function stack. Then jump to the callee function, that is, the command callcalle. When the CPU executes the call, it first presses the value of the current EIP Register into the stack, and then sets the EIP value to callee (Address). In this way, the stack diagram is changed to 2 (B ). The execution point of the program jumps to the first instruction of the callee function. When using C language for function calling, each function occupies a stack segment called stackframe. Use ebp to remember the starting address of the function stackframe. Therefore, when executing callee, the first two commands are pushebpmovesp. After ebp passes through these two statements, the stackframe of the callee function is created, as shown in the latest situation of stack 2 (c. The callee function defines a local variable int c. The storage space of this variable is allocated to the stack occupied by the callee function. The size is 4 bytes (insizeof int ). Then callee will be in the following command: subesp, 4mov [ebp-4], 0 this stack situation has changed again, the latest situation 2 (d. Note that esp always points to the top of the stack, while ebp plays a major role as the stackframe base address of the function. The space at the bottom of the ebp address is used to store local variables, and the space at the top is the parameters passed by caller. Of course, the compiler will remember the address offset of variable c relative to ebp, here is-4. Run the c = a + B statement. The command code is similar to moveax, [ebp + 8]. Here, eax is used to store the first passed parameter, remember that the offset between the first parameter and ebp must be 8 addeax, [ebp + 12]; The offset between the second parameter and ebp is 12, therefore, eax = a + bmov [ebp-4], eax; execute c = eax, that is, c = a + B Stack has a new change, 2 (e ). So far, the calculation command of the function callee has been executed, but there are still some things to do: Release the stack space occupied by local variables. The following command is generated during the stack-frame Process of the function: movesp, ebp: the space occupied by local variables is skipped, that is, it is no longer used. the space below ebp is all used for pop ebp; the stack-frame base address of the pop-up caller function can be replaced by the command leave in the two commands on the Intel CPU. The functions are the same. The stack content 2 (f) is shown in this way. Finally, the caller function is returned. Therefore, the last instruction of callee is ret. the ret instruction is used to pop up the saved breakpoint on the stack to the EIP register. The new stack content is 2 (g). The call and return of the function callee are all completed, followed by the execution of the next statement of callcallee. Before the caller function calls callee, it pushes the passed parameters to the stack in the order from right to left. When the function returns, callee does not clean up the stack, instead, caller clearly transmits the stack occupied by parameters (for example, when the function returns, 1 and 2 are still placed in the stack for caller to clean up ). The size of the stack element is 4 bytes. Each parameter occupies a multiple of 4 bytes of the stack space, and neither of the two parameters can share the same stack element. According to the function call conventions in C language, the parameter list is pressed from right to left, so the address of Variable Parameter pressure on the stack is larger than the last named parameter, as shown in 3: as shown in figure 3, variable parameters are placed on the last named parameter a, and each parameter occupies a multiple of 4. Therefore, the address of Variable Parameter 1 = the address of parameter a + the size of the stack occupied by parameter a, the address of Variable Parameter 2 = address of Variable Parameter 1 + the size of the stack occupied by Variable Parameter 1, variable Parameter 3 address = Variable Parameter 2 address + Variable Parameter 2 occupies the stack size, and so on. How can we calculate the size of each parameter's stack? 2. data Alignment problem for two positive integers x, n total exist integer q, r makes x = nq + r, where 0 <= r <n // minimum non-Negative residual q, r is uniquely identified. Q = [x/n], r = x-n [x/n]. This is a simple form with remainder division. In c, q and r are easy to calculate: q = x/n, r = x % n. alignment of x by n means: If r = 0, take qn. If r> 0, take (q + 1) n. this is equivalent to representing x as: x = nq + R', where-n <R' <= 0 // The maximum number of non-positive nq is what we want. The key is how to calculate it in c language. Since we can process the standard Division with remainder, we can convert this formula into a standard Division with remainder, and then process it: x + n = qn + (n + R '), where 0 <n + R' <= n // The largest positive remainder x + n-1 = qn + (n + R'-1 ), where 0 <= n + R'-1 <n // the minimum non-negative residue, so qn = [(x + N-1)/n] n. calculated in c language: (x + N-1)/n) * n if n is the power of 2, for example, 2 ^ m, it is removed from the right m bit, multiplication is to move m places left. So we can clear the minimum m binary bits of x + n-1 to 0. Obtained: (x + N-1 )&(~ (N-1) based on these derivation, I believe we have learned # define _ va_rounded_size (TYPE) (sizeof (TYPE) + sizeof (int)-1)/sizeof (int )) * sizeof (int. 3. let's look at the va _ * macro to define va_start (va_listap, last) last as the last named parameter. The va_start macro enables the ap to record the address of the first variable parameter, the principle is the same as that of "Address of Variable Parameter 1 = address of parameter a + size of stack occupied by. Starting from the memory address recorded by the ap, the Data type of the parameter is considered as type and its value is read out. The address of the ap record is directed to the next parameter, that is, the address of the ap record + = occupy_stack (type) va_arg (va_litap, type) Here is to obtain the variable parameter value, the specific work is: read the type parameter from the stack memory pointed to by the ap, and let the ap record its next variable parameter address based on the type size, so that the va_arg macro can be used again. Starting from the memory address recorded by the ap, the stored data type is considered as type and its value is read out. The address of the ap record is directed to the next parameter, that is, the address of the ap record + = occupy_stack (type) va_end (va_listap) is used to "release" the ap variable, which is symmetric with va_start. Va_start must have va_end in the same function. Variable Parameter macros define printf () and fprintf () parameters of these output functions are variable. When debugging a program, you may want to define your own Variable Parameter output functions, the Variable Parameter macro is an option. The macro in C99 can contain variable parameters like a function, such as # define LOG (format ,...) fprintf (stdout, format, _ VA_ARGS _) where ,... variable Parameter, __va_args _ in preprocessing, replacing the actual parameter set with GCC also supports the following form # define LOG (format, args ...) the usage of fprintf (stdout, format, args) is basically the same as the above, but it is only worth noting that variable parameters cannot be omitted in the macro definition above, although you can pass an empty parameter, it is necessary to mention the usage of the "#" Connection Symbol. "##" Is used to connect token. In the preceding example, format, _ VA_ARGS _, and args are tokens. If token is empty, no connection is made, therefore, you can omit variable parameters (_ VA_ARGS _ and args) and modify the macro of the variable parameters as follows # define LOG (format ,...) fprintf (stdout, format, ##__ VA_ARGS _) # define LOG (format, args ...) fprintf (stdout, format, # args) The above Variable Parameter macro definition can not only customize the output format, but also work with # ifdef # else # endif in the output management, for example, the debugging information is output during debugging, And the debugging information is not output during official release. You can do this by # ifdef DEBUG # define LOG (format ,...) fprintf (stdout, ">" format "\ n", ##__ VA_ARGS _) # else # Define LOG (format ,...) # In the debugging environment of endif, the LOG macro is a variable parameter output macro, Which is output in a custom format. In the publishing environment, the LOG macro is an empty macro and does nothing. Log writing instance # ifndef _ HYC_LOG_H _ # define _ HYC_LOG_H _ # include "base. h "# include <stdarg. h >#include <string >#include <iostream> // # ifdef LOG_COUT # define LOG_TRACE (strMsg ,...) \ {\ char ch [1024]; \ sprintf (ch, "% s % d % s" ,__ FILE __,__ LINE __,__ DATE __, __time _); \ std: cout <ch <"" <minprintf (strMsg, ##__ VA_ARGS _) <std: endl; \}# else // # define LOG_TRACE (strMsg ,...) \ {\ char ch [1024]; \ sprintf (ch, "% s % D % s ",__ FILE __,__ LINE __,__ DATE __,__ TIME _); \ char fileName [] =" huangxw. log "; \ std: ofstream outFile (fileName, std: ios: out | std: ios: app ); \ outFile <ch <"<minprintf (strMsg, ##__ VA_ARGS _) <std: endl; \ outFile. close () ;\}# endif // std: string minprintf (char * fmt ,...) {va_list ap;/* points to each unnamed arg in turn */char * p, * sval; char tVal [128]; int ival; double dval; std:: String strTotal; va_start (ap, fmt);/* make ap point to 1st unnamed arg */for (p = fmt; * p; p ++) {if (* p! = '%') {// Putchar (* p); strTotal + = * p; continue;} switch (* ++ p) {case 'D ': ival = va_arg (ap, int); sprintf (tVal, "% d", ival); strTotal + = tVal; break; case 'X': ival = va_arg (ap, int); sprintf (tVal, "% # x", ival); strTotal + = tVal; break; case 'F': dval = va_arg (ap, double ); sprintf (tVal, "% f", dval); strTotal + = tVal; break; case's ': for (sval = va_arg (ap, char *); * sval; sval ++) strTotal + = * sval; break; default: strTotal + = * p; break;} www.2cto.com va_end (ap ); /* clean up when done */return strTotal;} # endif test program: # include ".. /hyc_log.h "int main () {LOG_TRACE (" test: % d ", 10); LOG_TRACE (" test: % s = % d "," max ", 10 ); LOG_TRACE ("test ");}