Showing posts with label C99. Show all posts
Showing posts with label C99. Show all posts

Monday, June 3, 2013

Variadic macros and trailing commas

One of the great new (ok, 14-year old) features of C99 is the variadic macro. This feature finally provides a portable way to write a logging macro, for example. Here's a simple example:

#define LOGIFY(format, ...) \
    fprintf(stderr, "LOG: " format "\n", \
    __VA_ARGS__)
...
LOGIFY("Found %d widgets.", widgetCount);
...

However, there's a subtle problem here. What if we call LOGIFY with no variadic arguments?

LOGIFY("Finished.");

This expands to:

fprintf(stderr, "LOG: Finished.\n",);

And that doesn't compile, because there's a trailing comma.

GCC provides an extension to the language which works around this problem. For that compiler, you can use token pasting to make the comma magically disappear:

#define LOGIFY(format, ...) \
    fprintf(stderr, "LOG: " format "\n", \
    ##__VA_ARGS__)

However this is non-portable.

StackOverflow suggests that you can make the format part of the variadic part of the macro:

#define LOGIFY(...) \
    fprintf(stderr, "LOG: " __VA_ARGS__)

However this doesn't work for my example because I want to be able to past a new line onto the end of the format string. Since the format string isn't isolated from the arguments I can't append anything to it.

An ugly work-around is to simply pass in an extra, unused argument if you have no real arguments:

LOGIFY("Finished.", 0);

It's always harmless to pass extra arguments to a variadic function, and this does compile, but it doesn't seem very elegant.

If the ANSI C committee ever revisits this, or if I design my own language, they (or I) could resolve this problem by permitting trailing commas in function calls (and function declarations). C already permits trailing commas in array initializers (as do Java and other C-like languages):

int fibs[] = {1,1,2,3,5,8,13,};

This is convenient for a number of reasons, including conditional inclusion (#ifdef) and source code generation. It can also be a good habit to always include a trailing comma, especially when declaring arrays of strings. Without a trailing comma, there's a risk that a developer might add a new string to the end of the list without a comma. Since CPP does string concatenation, the results may be unexpected!

const char * messages[] = {
    "No error",           // 0; added in 2002
    "First legacy error", // added in 2002
    "Other legacy error"  // added in 2002
    "Brand new error"     // new in 2013
};

The same problem can occur with numbers if sign tokens (+/-) are used.

Why not support trailing commas in functions, too? It would be more consistent, and there are a number of not-immediately-obvious advantages. In addition to this variadic macro case, consider the case where one or more of the arguments is conditionally included:

void
doWork(
    int howMuchWork,
    WorkType whatKindOfWork,
#ifdef THREAD_SAFE
    bool useLocks
#endif
)
...

This doesn't compile if THREAD_SAFE isn't defined because there's an unused comma after the second argument. If C permitted trailing commas here we'd avoid awkward constructs like moving the comma into the conditional code (and even that wouldn't work if each of the arguments were conditional!).

Although it's not consistent with the normal usage of commas in prose the trailing comma is surprisingly useful in programming languages, and ought to be supported more widely.

Sunday, March 25, 2012

C99 designated initializers

One of the very nice features added in C99 is the designated initializer. This allows you to write code like the following to initialize a structure:

    div_t d = { .quot=3, .rem=2 };

In C89 there was no way to reliably initialize a structure like this. The specification says that the quot and rem members may be in any order, so if you write:

    div_t d = { 3, 2 };

you can't be sure which member will be 3 and which will be 2.

In general I'm enamored with designated initializers. But I've run into an unfortunate case where the specification is somewhat ambiguous.

Paragraph §6.7.8.19 of the C99 standard (draft version available for free here) has this to say about the ordering of designated initializers:

  1. The initialization shall occur in initializer list order, each initializer provided for a particular subobject overriding any previously listed initializer for the same subobject;130 all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
(Footnote 130 reads "Any initializer for the subobject which is overridden and so not used to initialize that subobject might not be evaluated at all.")

The spec says that "initialization shall occur in initializer list order". To me, this suggests that one initializer can safely rely on the result of a previous initializer, e.g.:

    div_t d = { .quot=42, .rem=d.quot };

So the following program ought to only invoke well defined behaviour, right?

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    div_t d1 = { .quot=1, .rem=2, };
    printf("d1: quot=%i, rem=%i\n", d1.quot, d1.rem);

    div_t d2 = { .quot=1, .rem=d2.quot };
    printf("d2: quot=%i, rem=%i\n", d2.quot, d2.rem);

    div_t d3 = { .rem=2, .quot=d3.rem };
    printf("d3: quot=%i, rem=%i\n", d3.quot, d3.rem);

    return 0;
}

Let's compile it and see what happens:
  1. $ gcc -c99 -O3 -Wall -Wextra foo.c
    $ ./a.out
    d1: quot=1, rem=2
    d2: quot=1, rem=1
    d3: quot=2, rem=2

Excellent! That exactly what I would expect to happen. The initializers are run in order, allowing the second designated initializer to depend on the result of the first.

Now here's where things start to get weird:
  1. $ gcc -c99 -O0 -Wall -Wextra foo.c
    $ ./a.out
    d1: quot=1, rem=2
    d2: quot=1, rem=0
    d3: quot=0, rem=2

If we turn off optimization (-O0) the results suddenly change! Even worse, I've compiled with maximum warnings (-Wall -Wextra) and GCC doesn't even issue a warning about using an uninitialized variable!

How can we reconcile this behaviour with the specification? I think that we need to take paragraph §6.7.8.23 into account, as well:

  1. The order in which any side effects occur among the initialization list expressions is unspecified.131

(Footnote 131 reads "In particular, the evaluation order need not be the same as the order of subobject initialization.")

This suggests that evaluation and initialization are two separate steps: an implementation may evaluate all of the initialization expressions (in any order), record the results in temporary storage, and then apply them all in order. If you inspect the generated assembly code this does seem to match what happens in GCC at -O0.

So why have paragraph 19 at all? I don't see how you could write a C program which can observe the initialization order, except for the special case of one designated initializer overriding another. If that is its only purpose the specification could certainly be more explicit about it. (It's also possible that the specification has actually been clarified in this respect; I don't have access to the final version.)

I've tried this test case with a handful of different compilers and get similar results. However I would be curious to hear about results with other C99 compilers.

Saturday, August 14, 2010

Picket fence comments

Here's a stupid trick for C++ or C99 programmers. Make your comments look like a picket fence:

// Here's the first line of my comment \\
\\ Here's the second line              //
// You can go on and on like this      \\
\\ but you have to be careful about    //
// how you end the comment, or the     \\
\\ next line might be commented out    //

Why does this work? You can't use \\ as a single line comment, can you?

Hint; this doesn't work:

// This is a comment
\\ But this isn't

Why can't you do this in C89? Because // comments weren't supported until the 1999 revision of the ISO C spec although a number of compilers "embraced and extended" the standard (I'm looking at you Microsoft and GNU).

Why would you do this? I guess you could use it to help document your Obfuscated C Code Contest entry. But in the end this is just another example of something you can do in C but shouldn't.