Very few programming languages allow the user to define functions with variable number of parameters. Although this method is very powerful, it should be used carefully. In this review we will explore some of the uses of variadic functions and macros, as well as common pitfalls.

Variadic Functions in C
A classic usage of a function that accepts a variable number of arguments is:

#include <stdarg.h>
#include <stdio.h>

enum {
    ERROR_LOG,
    WARN_LOG,
    INFO_LOG,
    VERBOSE_LOG,
    DEBUG_LOG
} log_levels;

const char *log_levels_str[] = {
    "Error",
    "Warning",
    "Info",
    "Verbose",
    "Debug",
};

int applog(int level, const char *fmt, ...)
{
    int ret;
    FILE *stream;
    va_list ap;
    va_start(ap, fmt);

    (level <= WARN_LOG) ? (stream = stderr) : (stream = stdout);
    fprintf(stream, "%s: ", log_levels_str[level]);
    ret = vfprintf(stream, fmt, ap);
    va_end(ap);
    return ret;
}

The call to va_start will initialize the variable ap right after fmt. The caller can then use ap to access variables via va_arg, or pass it to other functions.

GCC's 'format' __attribute__
GCC provides a nice protection against misuse of variadic functions in compile time. Just like warnings issued on the 'printf' family of functions, it is possible to protect your own implementation. For example:

//If the compiler does not support attributes, disable them
#ifndef __GNUC__
#   define  __attribute__(x)
#endif

int applog(int level, const char *fmt, ...) 
    __attribute__ ((format(printf, 2, 3) ));

The attribute will preform a compile time test for a printf style call, and will try to match argument #2 (fmt) as the format string. Argument #3 of the function will mark the beginning of the variadic arguments.

The format attribute also support the following validation methods: scanf, strftime and strfmon.

This attribute is supported in all GCC versions 2.x and up. For more information about this attribute (and others) see [1].

C++ quirks
When writing variadic functions in C++ you might think the following is correct:

class A {
...
    int myprintf(const char *fmt, ...)
        __attribute__((format(printf, 1, 2)));
};

The format attribute should use the first parameter as the format string and the second as the variable part. Compiling this in GCC will output the following error:

error: format string argument not a string type

This is of course due to the fact that C++ has an extra argument in all class members, this. So the correct declaration should be:

class A {
...
    int myprintf(const char *fmt, ...)
        __attribute__((format(printf, 2, 3)));
};

Accessing Variables Manually
Besides passing the va_list struct to various supporting functions, it is possible to iterate on the function's variables manually. This can be done similar to:

int sum_many(int first, ...)
    __attribute__((sentinel(0)));

int sum_many(int first, ...)
{
    int num, ret = first;
    va_list ap;
    va_start(ap, first);
    while ( (num = va_arg(ap, int)) != NULL) {
       ret += num;
    }
    va_end(ap);
    return ret;
}

Each call to va_arg will return the next argument. The va_arg function requires a typename (or a pointer to a typename) to cast the resulting argument.
The sentinel(0) attribute enforces a compile time check that the argument at position 0 (last argument of the function) is an explicit NULL.

Variadic Macros
Sometimes it can be very useful to have a macro with unknown number of variables. While this seems simple enough, it was only added as part of C99. Example usage follows:

#define LOG_DEBUG(fmt, ...) (applog(DEBUG_LOG, "[%s at %s:%u]: " fmt,  \
__FUNCTION__, __FILE__, __LINE__, __VA_ARGS__))

Variadic macros are supported by any C99 compliant compiler. Namely GCC 3.3 and above, Microsoft Windows Visual Studio 2005 and up (see [3]).

References:
[1] http://unixwiz.net/techtips/gnu-c-attributes.html
[2] http://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
[3] http://msdn2.microsoft.com/en-us/library/ms177415(VS.80).aspx

Posted by Ami Chayun | | No comments

No Comments »

RSS feed for comments on this post. TrackBack URI

Leave a comment