Skip to content
Nicholas Clark edited this page May 20, 2021 · 9 revisions

C99

This page exists for "requirements gathering". Once we're done, this page will be replaced by finished documentation in blead.

For various reasons C99 took a long time to become widely available. We still can't rely on all of it (certainly not the parts C11 made optional) but there are several features we'd like to use, if we know they work everywhere.

To clearly document what we can use, we need to figure this out - in particular, the trade off between "supporting awkward compilers" and "useful".

Hence we need to figure out what we can use, so we can document this.

We already support/fake

  • bool
  • static inline
  • static assert

(And even adding support for bool more than a decade after C99, we hit compiler bugs for things like converting a pointer to a bool, and bool in ternaries. This stuff hasn't been battle tested widely.)

We already exceed C89 minimally required limits on

  • line length after macro expansion
  • cases in a switch statement

without problems, so we shouldn't worry about staying strictly within a C89 straight jacket for these.

For what we might need gcc 3.1 or later is fine. 19 year old gcc will be fine...

Whilst not the default,

  • xlc on AIX officially got c99 support in July 2002 with version V6.0 -- -qlanglvl=stdc99
  • HP aC/C++ references C99 support in documents from 2004 -- -AC99
  • MSVC since Microsoft Visual C++ 2013 Express x86 (VC12.0)
  • VMS vendor compiler has most of the syntax, but library support has only arrived incrementally

As Craig A. Berry notes:

Last year there was a patch that finally introduced stdint.h, moving to it a lot of things that had been in inttypes.h and supplying a lot of things that were missing. fpclassify() got some attention and the %z format specifier finally became available. va_copy is still not available, but is expected to be on x86_64.

Open questions are:

// comments

  • Supported by VC6.0.

testcase

#include <stdio.h>

int main(int argc, char **argv) {
    // Comment
    puts("Built "
         // Comment
         "// comments");
    return 0;
}

benefits

None have been clearly explained.

flexible array members

  • Supported by all compilers we tested.

testcase

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

struct greeting {
    unsigned int len;
    char message[];
};

int main(int argc, char **argv) {
    const unsigned int len = strlen(argv[0]);
    struct greeting *g = malloc(sizeof(struct greeting) + len + 1);

    g->len = len;

    memcpy(g->message, argv[0], len);
    printf("Built flexible arrays (as %*s)\n", g->len, g->message);

    return 0;
}

benefits

This is standards conformant, unlike the "unwarranted chumminess with the compiler" hack we currently use of

struct greeting {
    unsigned int len;
    char message[1];
};

However, we use that hack already, and it works. So what do we gain?

Also, (IIRC) flexible array members are not allowed within nested structs or unions, whereas the hack we have works as the last member within nested aggregates.

64 bit integer types

(even if slow, and just for arithmetic and bitwise operators)

  • Needs Microsoft Visual C++ 2003 Toolkit x86 (VC7.1)

testcase

#include <stdio.h>

int main(int argc, char **argv) {
    unsigned long long hex = 18364758544493064720ULL;
    unsigned long long big = 0x9e3779b97f4a7c15;
    unsigned long small = 0x9e3779b7;
    puts("Built 64 bit integer types");
    printf("%llX >> 4 is %llX\n", hex, hex >> 4);
    printf("%llX * %lX is %llX\n", big, small, big * small);
    return 0;
}

benefits

  • Certain calculations can be expressed directly

variadic macros

  • Needs Microsoft Visual C++ 2005 Express x86 (VC8.0)

testcase

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

void greet(char *file, unsigned int line, char *format, ...) {
    va_list ap;

    va_start(ap, format);
    printf("I was called at %s %u to say: ", file, line);
    vprintf(format, ap);
    va_end(ap);
}

#define logged_greet(...) greet(__FILE__, __LINE__, __VA_ARGS__)

int main(int argc, char **argv) {
    char *action = "Built";
    char *source = "variadic macros";
    logged_greet("%s %s\n", action, source);
    logged_greet("argv[0] is %s\n", argv[0]);
    return 0;
}

benefits

  • very useful for making sprintf style defines actually DWIM with PERL_NO_CONTEXT

mixed declarations and code

  • Needs Microsoft Visual C++ 2013 Express x86 (VC12.0)

testcase

#include <stdio.h>

int main(int argc, char **argv) {
    char *action;
    action = "Built";
    const char *source = "mixed declarations and code";
    printf("%s %s\n", action, source);
    return 0;
}

benefits

  • Can directly reduce line count without reducing readability
  • Can indirectly make it easier to use const
  • We have some macros which introduce declarations - knowing the ordering conventions is a barrier to entry

declarations in for loops

  • Needs Microsoft Visual C++ 2013 Express x86 (VC12.0)
  • gcc 4.7 needed --std=c99 to compile this. Potentially --std=c99 might then warn about gcc-isms, so maybe --std=gnu99 would be better.

testcase

#include <stdio.h>

int main(int argc, char **argv) {
    char *message = "Built declarations in for loops\n";

    for (const char *p = message; *p; ++p) {
        putchar(*p);
    }
    return 0;
}

benefits

  • Can directly reduce line count without reducing readability
  • Can indirectly make it easier to use const

member structure initialisers

  • Needs Microsoft Visual C++ 2013 Express x86 (VC12.0)
  • Only recently added to C++, so we can't use them in headers

testcase

#include <stdio.h>

struct message {
    char *action;
    char *target;
};

void greet(struct message mcguffin) {
    printf("%s %s\n", mcguffin.action, mcguffin.target);
}

int main(int argc, char **argv) {
    struct message mcguffin = { .target = "member structure initialisers", .action = "Built" };
    greet(mcguffin);
    return 0;
}

benefits

  • Clearer code.
  • Less chance of errors.
  • Structures can be re-ordered without makework.

caveats

Beware - the test case is simplistic. It's quite possible there are bugs on some compilers once one tries to nest these. However, if we find these, it's:

  • Doctor doctor, it hurts when I do this.
  • Well, don't do that then.

and we use old-style syntax instead.

Nice that this feature is, the "lack of C++ support" might already be a big enough blocker to make it less than worthwhile.

variable length arrays

  • C11 made this optional.
  • Not supported by any MSVC.
  • We can't use these :-(
  • Tomasz Konojacki suggests that we set gcc to treat them as errors, if viable.

testcase

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

int main(int argc, char **argv) {
    const unsigned int len = strlen(argv[0]);
    char buffer[len];

    memcpy(buffer, argv[0], len);
    printf("Built variable length arrays (as %*s)\n", len, buffer);

    return 0;
}

benefits

  • avoids a call to malloc
  • avoids missing calls to free, particularly on error paths

risks

  • undefined behaviour if you overflow the stack
  • no portable way to figure out your stack size