Puzzle 6

No Polymorphism

We all know C doesn’t support polymorphism. You all have taken CS 10 in Java, so you know what that means. It means you can’t have something like the following:

int sum(int a, int b);
double sum(double a, double b);
int sum(int a, int b, int c);
double sum(double a, double b, double c);

Nope. We have to have independent sum functions like this:

int sum2int(int a, int b);
double sum2double(double a, double b);

You get the point.

Anyone disagree / find a way to do polymorphism in C? No? Okay, so we all agree. This is a limitation on the language.

Variadic Functions

Well if that’s the case, how does a function like printf exist? Isn’t this a perfect example of a function that takes multiple arguments? Hmm. This is today’s puzzle.

As it turns out, there is another feature of C that you haven’t learned about! Functions in C can accept a variable number of arguments. These are known as variadic functions.

Let’s take a look at the file stuff.c:

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

double f1(int argNum, ...) {
    va_list arguments;
    va_start(arguments, argNum);
    
    double sum = 0;
    for (int i = 0; i < argNum; i++)
        sum += va_arg(arguments, int);
        
    va_end(arguments);
    
    return sum / argNum;
}

int main(int argc, const char *argv[]) {
    printf("average: %lf\n", f1(4, 1, 2, 3, 4));
    return 0;
}

Pretty cool, eh? The ellipses ... are used to indicate a function takes a variable number of arguments. Note that the compiler cannot know how many arguments are being passed, so the first argument must be the number of arguments. The function printf gets around this by having the first parameter be the format of the string, and the code then deduces how many parameters there must be from the format of the string.

Note the inclusion of stdarg.h which is required to define the va_list structure along with va_start, va_arg, and va_end functions.

In Java terms, va_list arguments is declaring an iterator arguments, while the line va_start(arguments, argNum) is establishing how many arguments there are to iterate over.

The actual .next() “method” of the iterator is called using va_arg, which simply returns the next argument.

Once this is done, you call va_end to cleanup any memory associated with the argument parsing. Obviously, the code prints 2.5000 as the result.

So is this polymorphism? No, not really. Note that the second argument for va_arg is the type of the argument! The compiler really does not know what the argument can be. If the caller has to specify the type of the arguments at every point of invocation, then it removes the entire benefit of polymorphism in the first place. This is the first step necessary to achieve polymorphism, but until you can deduce the type you cannot achieve the full power of polymorphism.

Another useful function in this set is vsprintf. You can use this to copy the contents of the variable arguments into a character buffer, just like sprintf.

Here is an example.

void function(const char *fmt, ...) {
    // setup
    char buffer[MAX_BUFFER_SIZE];
    va_list arguments;
    va_start(arguments, fmt);
    
    // copy content
    vsprintf(buffer, fmt, arguments);
    
    // cleanup
    va_end(arguments);
    
    fprintf(stdout, "%s\n", buffer);
}

Variadic Macros

One of the new features introduced in the new C99 standard is variadic macros. As the name suggests, they are macros that also take a various number of arguments.

You may remember from two recitations ago that Vipul mentioned you can use __FILE__ and __LINE__ within your print statements to help debug what file and what line this particular statement is printing from. That’s pretty cool.

But what if we could create a wrapper around printf that did that for us automatically so we could do even less work?

Here is our wrapper around printf and the macro that calls the wrapper:

#define dbgprintf(...) realdbgprintf (__FILE__, __LINE__, __VA_ARGS__)
void realdbgprintf (const char *SourceFilename, int SourceLineno, const char *CFormatString, ...);

Nice! How does this work? The call

dbgprintf ("Hello, world");

expands out to

realdbgprintf (__FILE__, __LINE__, "Hello, world");

Another example? Why not.

dbgprintf("%d + %d = %d", 2, 2, 5);

becomes

realdbgprintf(__FILE__, __LINE__, "%d + %d = %d", 2, 2, 5);

Without variadic macros, writing wrappers to printf is not directly possible. – The Wisdom of Wikipedia

But what did people do before the existance of C99 standard that allowed this feature within macros? Remember that old parentheses trick with macros? We exploit the same thing.

#define dbgprintf(x) realdbgprintf x

Then you would call it with something like this:

dbgprintf (("Hello, world %d", 27));

Which would expand to

realdbgprintf ("Hello, world %d", 27);

Which is what we wanted! Cute.

Resources

Reading is good! Sort of. Minimal reading is good – we’re not the humanities. So here is some minimal reading.

Understanding va_list

Variadic functions in C

Example above came directly from C++ . com and Alex Allain’s brilliant tutorials. If you haven’t guessed by now, I’m a big fan of Alex.

Variadic macros

Interesting Case – read this