In C, it is possible to declare and use functions with a variable number of arguments. This possibility is provided by the features of working with the stack when calling functions, but now they will not be considered in detail, only the practical side of the issue. The magic that happens in <stdarg.h> also outside the <stdarg.h> , curious, and will find an explanation for this shamanism.
The prototype of such a function might look like this, for example:
void print_messages( const char * title, ... );
And the challenge is:
print_messages( "Вот что имею сообщить", "это раз", "это два", "это три", NULL );
And print accordingly:
Вот что имею сообщить: - это раз. - это два. - это три.
Well, its implementation:
void print_messages( const char * title, ... ) { va_list ap; const char * message; va_start( ap, title ); printf( "%s:\n", title ); message = va_arg( ap, const char * ); while( message ) { printf( " - %s.\n", message ); message = va_arg( ap, const char *); } va_end( ap ); }
The most important issue here is determining the end of the list of arguments. In this case, NULL used to mark it. But this is not always acceptable. For example, when NULL is a valid argument value. Or, say, 0 / -1 in the case of integers.
The first solution to this problem is to pass the number of arguments to the first parameter:
void print_numbers( size_t amount, ... ) { va_list ap; int number; va_start( ap, amount ); printf( "Total numbers: %zu, let's go! ", amount ); while( amount-- ) { number = va_arg( ap, int ); printf( " [%d]", number ); } va_end( ap ); }
Call:
print_numbers( 3, 11, 22, 33 );
This method can be used only when arguments of the same type are passed to the function. The same can be achieved by passing an array of values with its size. But this is not always justified. Yes, and now considered a technological demo, and not expediency (which is always on the conscience of the programmer, but then /dev/brain attached to it).
Whisper: well, TMTOWTDI also happens in hi, hi-hi ...
And what if the arguments can be of different types? This is where a method called format comes to the rescue. Any sishnik is familiar with it by *printf* . But it is not interesting. We will invent our own, on the same principle.
We define:
- the character 'c' means char
- 's' - short
- 'l' - long
- 'z' - char *
Prototype:
void print_something( const char * format, ... );
Implementation:
void print_something(const char * fmt, ...) { va_list ap; va_start( ap, fmt ); /* Гусары, молчать! */ long l; int i; char c; char *z; while( *fmt ) { switch( *fmt ) { case 'c': /* см дальше */ c = (char)va_arg( ap, char ); printf( "char: '%c'\n", c ); break; case 's': /* см дальше */ s = (short)va_arg( ap, short ); printf( "short: '%d'\n", s ); break; case 'i': i = va_arg( ap, int ); printf( "int: '%d'\n", i ); break; case 'l': l = va_arg( ap, long ); printf( "long: '%lu'\n", l );; break; case 'z': z = va_arg( ap, char * ); printf( "char *: '%s'\n", z );; break; default: printf( "Хрен знает что передали: '%c'\n", *fmt ); break; } fmt++; } }
Next: char , short and so on, which is less than int .
These lines excite the compiler:
c = (char)va_arg( ap, char ); /* 'char' is promoted to 'int' when passed through '...' */ s = (short)va_arg( ap, short ); /* 'short' is promoted to 'int' when passed through '...' */
I will not draw conclusions, I will leave on a homework.