Understanding and Using Variable Parameter va_list
During the C language code writing process, printf is often used to format the print. This function supports a variety of parameters and variable parameters. All variable parameters are implemented using va_list. The following is usually used: va_list vp; va_start (vp, arg1); typevalue1 = va_arg (vp, typename1 );... typevalueN = va_arg (vp, typenameN); va_end (vp );
The above is how to use variable parameters. The function defining variable parameters must have at least one definite parameter. For example, the declaration of the function printf is as follows: int printf (const char * format ,...);
Format is a required parameter; otherwise, the function compilation fails. From the code format above, we can call va_arg () multiple times to obtain the corresponding parameter value. You also need to know the corresponding value type for obtaining this value. Multiple calls to this interface also require the number of variable parameters, which must be passed in some form. Therefore, to use va_list, you must know the number of parameters and the corresponding parameter type.
These parameters are actually passed when the variable parameter interface printf is usually exposed, because a series of formats are usually set when the printf () function is called, these formats indicate the number of variable parameters and the corresponding parameter types, in this way, printf can determine the type through the formatted symbols, and the number of escape characters determines the number of variable parameters. Printf ("% d, % d \ n", a, B, c, d); the number of % d is known, the number of variable parameters is four, and % d indicates an integer. Therefore, the required type corresponds to int.
Here is a brief introduction to the va_list implementation method: in C language, the stack is a very important part. Only by understanding the composition of the stack can we understand the call process of the program, this section briefly introduces the stack form in the function call process. The basic model is as follows:
The basic model of function call is as follows. The parameter can be used to locate the corresponding parameter location through the current stack frame location, that is, the parameter value passed by the function that calls the current function can be found based on the location of Calledfunc_EBP. These parameter values are stored in the stack space. the space occupied by the parameter and the storage location of the current parameter can be used to calculate the location of other parameters. Because there must be a known definite parameter in va_list, this parameter is actually used to determine the starting position of a variable parameter. The basic principles for determining this parameter are shown in. Of course, this diagram is just a model diagram, and there may be some differences in implementation. The detailed description is not provided here.
Here is a simple method for calculating the sum of variable integer integers. The size parameter is used to save the number of parameters. Because it is an integer, va_arg directly processes the int type.
- #include
- #include
- #include
- #include
- int f(int x, int y, int z)
- {
- return x + y + z;
- }
- int sum(int size, ...)
- {
- va_list vp;
- int s = 0;
- int i = 0;
- va_start(vp, size);
- for (i = 0; i < size; ++ i) {
- s += va_arg(vp, int);
- }
- va_end(vp);
- printf("%d,%d\n", size, s);
- }
- int main()
- {
- int a = 1;
- int b = 2;
- int c = 3;
- printf("%d\n", f(a, b , c));
- sum(5, 20, 30, 40, 50, 20);
- return 0;
- }
There are still many usage of va_list, and there are a lot of processing in the Code related to message parsing. This example is to get the same type of data, there may be various formats in formatting, you only need to obtain parameters based on different formats to complete the processing of complex formats. For this part, refer to the redis-cli processing logic for cli, as shown below:
- Void * redisCommand (redisContext * c, const char * format ,...){
- Va_list ap;
- Void * reply = NULL;
- Va_start (ap, format); // The relative start address of a variable parameter that has been formatted.
- Reply = redisvCommand (c, format, ap); // process the ap
- Va_end (ap );
- Return reply;
- }
Next, view the ap-related operation logic:
- Int redisvFormatCommand (char ** target, const char * format, va_list ap ){
- Const char * c = format;
- Char * cmd = NULL;/* final command */
- Int pos;/* position in final command */
- Sds curarg, newarg;/* current argument */
- Int touched = 0;/* was the current argument touched? */
- Char ** curargv = NULL, ** newargv = NULL;
- Int argc = 0;
- Int totlen = 0;
- Int j;
- /* Abort if there is not target to set */
- If (target = NULL)
- Return-1;
- /* Build the command string accordingly to protocol */
- Curarg = sdsempty ();
- If (curarg = NULL)
- Return-1;
- While (* c! = '\ 0 '){
- If (* c! = '%' | C [1] = '\ 0 '){
- If (* c = ''){
- If (touched ){
- Newargv = realloc (curargv, sizeof (char *) * (argc + 1 ));
- If (newargv = NULL) goto err;
- Curargv = newargv;
- Curargv [argc ++] = curarg;
- Totlen + = bulklen (sdslen (curarg ));
- /* Curarg is put in argv so it can be overwritten .*/
- Curarg = sdsempty ();
- If (curarg = NULL) goto err;
- Touched = 0;
- }
- } Else {
- Newarg = sdscatlen (curarg, c, 1 );
- If (newarg = NULL) goto err;
- Curarg = newarg;
- Touched = 1;
- }
- } Else {
- Char * arg;
- Size_t size;
- /* Set newarg so it can be checked even if it is not touched .*/
- Newarg = curarg;
- Switch (c [1]) {
- Case's ': // string processing
- Arg = va_arg (ap, char *);
- Size = strlen (arg );
- If (size> 0)
- Newarg = sdscatlen (curarg, arg, size );
- Break;
- Case 'B ':
- Arg = va_arg (ap, char *); // obtain the string address first.
- Size = va_arg (ap, size_t); // obtain the length of the string
- If (size> 0)
- Newarg = sdscatlen (curarg, arg, size );
- Break;
- Case '% ':
- Newarg = sdscat (curarg, "% ");
- Break;
- Default:
- /* Try to detect printf format */
- {
- Static const char intfmts [] = "diouxX ";
- Char _ format [16];
- Const char * _ p = c + 1;
- Size_t _ l = 0;
- Va_list _ cpy;
- /* Flags */
- If (* _ p! = '\ 0' & * _ p =' # ') _ p ++;
- If (* _ p! = '\ 0' & * _ p = '0') _ p ++;
- If (* _ p! = '\ 0' & * _ p ='-') _ p ++;
- If (* _ p! = '\ 0' & * _ p = '') _ p ++;
- If (* _ p! = '\ 0' & * _ p =' + ') _ p ++;
- /* Field width */
- While (* _ p! = '\ 0' & isdigit (* _ p) _ p ++;
- /* Precision */
- If (* _ p = '.'){
- _ P ++;
- While (* _ p! = '\ 0' & isdigit (* _ p) _ p ++;
- }
- /* Copy va_list before consuming with va_arg */
- Va_copy (_ cpy, ap );
- /* Integer conversion (without modifiers )*/
- If (strchr (intfmts, * _ p )! = NULL ){
- Va_arg (ap, int); // obtain the Parameter
- Goto fmt_valid;
- }
- /* Double conversion (without modifiers )*/
- If (strchr ("eEfFgGaA", * _ p )! = NULL ){
- Va_arg (ap, double); // get floating point number
- Goto fmt_valid;
- }
- /* Size: char */
- If (_ p [0] = 'H' & _ p [1] = 'H '){
- _ P + = 2;
- If (* _ p! = '\ 0' & strchr (intfmts, * _ p )! = NULL ){
- Va_arg (ap, int);/* char gets promoted to int */
- Goto fmt_valid;
- }
- Goto fmt_invalid;
- }
- /* Size: short */
- If (_ p [0] = 'H '){
- _ P + = 1;
- If (* _ p! = '\ 0' & strchr (intfmts, * _ p )! = NULL ){
- Va_arg (ap, int);/* short gets promoted to int */
- Goto fmt_valid;
- }
- Goto fmt_invalid;
- }
- /* Size: long */
- If (_ p [0] = 'l' & _ p [1] = 'l '){
- _ P + = 2;
- If (* _ p! = '\ 0' & strchr (intfmts, * _ p )! = NULL ){
- Va_arg (ap, long );
- Goto fmt_valid;
- }
- Goto fmt_invalid;
- }
- /* Size: long */
- If (_ p [0] = 'l '){
- _ P + = 1;
- If (* _ p! = '\ 0' & strchr (intfmts, * _ p )! = NULL ){
- Va_arg (ap, long );
- Goto fmt_valid;
- }
- Goto fmt_invalid;
- }
- Fmt_invalid:
- Va_end (_ cpy );
- Goto err;
- Fmt_valid:
- _ L = (_ p + 1)-c;
- If (_ l <sizeof (_ format)-2 ){
- Memcpy (_ format, c, _ l );
- _ Format [_ l] = '\ 0 ';
- Newarg = sdscatvprintf (curarg, _ format, _ cpy );
- /* Update current position (note: outer blocks
- * Increment c twice so compensate here )*/
- C = _ P-1;
- }
- Va_end (_ cpy );
- Break;
- }
- }
- If (newarg = NULL) goto err;
- Curarg = newarg;
- Touched = 1;
- C ++;
- }
- C ++;
- }
- /* Add the last argument if needed */
- If (touched ){
- Newargv = realloc (curargv, sizeof (char *) * (argc + 1 ));
- If (newargv = NULL) goto err;
- Curargv = newargv;
- Curargv [argc ++] = curarg;
- Totlen + = bulklen (sdslen (curarg ));
- } Else {
- Sdsfree (curarg );
- }
- /* Clear curarg because it was put in curargv or was free 'd .*/
- Curarg = NULL;
- /* Add bytes needed to hold multi bulk count */
- Totlen + = 1 + intlen (argc) + 2;
- /* Build the command at protocol level */
- Cmd = malloc (totlen + 1 );
- If (cmd = NULL) goto err;
- Pos = sprintf (cmd, "* % d \ r \ n", argc );
- For (j = 0; j <argc; j ++ ){
- Pos + = sprintf (cmd + pos, "$ % zu \ r \ n", sdslen (curargv [j]);
- Memcpy (cmd + pos, curargv [j], sdslen (curargv [j]);
- Pos + = sdslen (curargv [j]);
- Sdsfree (curargv [j]);
- Cmd [pos ++] = '\ R ';
- Cmd [pos ++] = '\ n ';
- }
- Assert (pos = totlen );
- Cmd [pos] = '\ 0 ';
- Free (curargv );
- * Target = cmd;
- Return totlen;
- Err:
- While (argc --)
- Sdsfree (curargv [argc]);
- Free (curargv );
- If (curarg! = NULL)
- Sdsfree (curarg );
- /* No need to check cmd since it is the last statement that can fail,
- * But do it anyway to be as defensive as possible .*/
- If (cmd! = NULL)
- Free (cmd );
- Return-1;
- }
From the above Code, we actually perform repeated operations on va_list to obtain the specific content. Through va_list, you can easily solve the formatting operation.