P99
Programming conventions

P99 uses some programming conventions that might be interesting for projects that include its header files.

  1. Standard conformance
  2. Operating system independence
  3. Defining identifiers
  4. Variable initialization
  5. Use of temporary lvalues

Standard conformance

Where we can, we try to conform to the C99 standard and to mark extensions clearly, if we use them.

Undefined behavior

The C specification has many places where it explicitly says that under certain circumstances the behavior of the resulting code is undefined. Generally this means that a conforming C implementation is not obliged to capture such circumstances and for code that uses such undefined behavior might do anything, from do-the-right-thing or crashing to eating your hard drive.

P99 should not produce any such undefined behavior.

Implementation specific behavior

In other places the standard leaves room for C implementations to specify certain behavior.

P99 tries not use any special feature that might be the result of such implementation specific behavior. This concerns in particular arithmetic on integer types. Here the standard allows certain variations:

  • padding bits: integer types may have padding bits that do not count towards their width (# of significant bits) but do count towards their size (storage requirement). So generally we have to be careful to not use expressions that use sizeof expressions for shifts.
  • encoding of signed types: C99 allows three different encodings for signed integers. We do not assume any of these encodings but build macros that are valid for all of them.
  • signed under- and overflow: arithmetic on signed integer types may under- or overflow and C99 leaves it to the implementation whether or not this silently wraps around or triggers a signal. All expressions that involve signed types should be such that they avoid this implementation specific behavior. E.g to compute the absolute value of a negative int a we would use -(unsigned)a. This expression guarantees that the result is well defined even for corner cases (here a being INT_MIN in two's complement representation) and will never trigger a range error.
  • We do not suppose the presence of the typedefs uintptr_t or intptr_t since they are optional in C. In particular we may not assume that there is any sensible conversion between pointers and integer types.

Defining identifiers

Macro names that implement the functionality of P99 are generally uppercase. Exceptions from that rule are Macros that hide a function. All other identifiers are lowercase.

P99 uses the common prefixes P99_ and p99_ for macros and other identifiers, respectively. Future P99 versions could define new identifiers with these prefixes. If you include any of the P99 files, avoid using these prefixes for your own identifiers.

The same rule holds for the prefixes P00_and p00_ which are used for auxilliary identifiers that need not be documented. Such identifiers are ignored in the doxygen documentation.

Operating system independence

The P99 macros and functions as such should be independent of the execution system and compiler. Nevertheless, for the time being they are only tested on POSIX systems, namely Linux. So if problems are discovered with other systems, please let us know.

In contrast to that general policy, there is one file that is dependent on the system, p99_posix_default.h. As the name indicates it is designed for POSIX systems and provides default arguments for some POSIX functions.

Also, some of the examples throughout this documentation are taken from programs that would typically run on POSIX systems. We hope that such examples are obvious and don't need explanation for programmers of other systems.

Variable initialization

Where possible, P99 uses initializers to initialize variables. For each type T where such an initialization is possible, there should be a macro T_INITIALIZER that does a standard initialization. Such a macro should use the Designated initializers scheme.

typedef struct toto toto;
struct toto { double a; unsigned b; };
#define TOTO_INITIALIZER { .a = 0.0; .b = 0u }

In case you want the default behavior of C, namely that all fields are recursively initialized with 0 then you could just use

#define TOTO_INITIALIZER P99_INIT

to make this choice explicit.

Such initializers can easily be assembled together

typedef struct tutu tutu;
struct tutu { toto A; bool c; };
#define TUTU_INITIALIZER(VAL) { .A = TOTO_INITIALIZER, .c = (VAL) }

As you can see in this example, INITIALIZER can be a ‘normal’ macro or a function like macro.

For dynamic initialization we assume that an ‘init’ function exists that

  • takes a pointer as a first argument
  • tests for the validity of that pointer, and
  • returns exactly the same pointer
    toto* toto_init(toto* t) {
    // assign from a compound literal
    if (t) *t = (toto)TOTO_INITIALIZER;
    return t;
    }
    tutu* tutu_init(tutu* t, bool val) {
    if (t) {
    toto_init(&(t->A));
    t->c = val;
    }
    return t;
    }

Use of temporary lvalues

Often when programming utilities for C that are supposed to return a pointer to an array or structure, the question of who is allocating the space arises: the caller or the callee.

P99 goes a different way, in that it tries to remove most of the burden from the programmer of both caller and callee. Let us look at the hypothetical function

char const* hostname(char buffer[], size_t len);

which could be defined as being similar to the POSIX gethostname function, only that it doesn't return an error indicator but a pointer to the name or a null pointer if it fails. An old time (and dangerous!) calling convention for such a function would perhaps have been to return a statically allocated buffer in case that the buffer argument is a null pointer.

P99 lets you define more convenient and less dangerous calling conventions: Default arguments to functions allows us to define a macro of the same name that uses a compound litteral if no argument is given to the same function.

#define hostname(...) P99_CALL_DEFARG(hostname, 2, __VA_ARGS__)
#define hostname_defarg_0() P99_LVAL(char[HOSTNAME_MAX])
#define hostname_defarg_1() HOST_NAME_MAX

This defines three different macros. One that is used where the programmer places a call to hostname. The other two, hostname_defarg_0 and hostname_defarg_1, are used by the macro hostname when the respective arguments are left out.

Now hostname can be used in three different ways.

  1. Such that the caller is responsible and obtains space on the heap:
    char const*const host = hostname(malloc(mylen), mylen);
    .
    free(host);
  2. Such that the caller initializes its own variable that has a storage class that best fits its needs:
    char host[mylen];
    .
    hostname(host, mylen);
  3. Or such that the space is allocated on the stack of the current call scope:
    char const*const host = hostname();

The later is then equivalent to

char tmp[HOSTNAME_MAX] = { 0 };
char const*const host = hostname(tmp, HOSTNAME_MAX);

but without leaving a non-const access to the contents of tmp.

It uses a temporary value that is only valid inside the block in which the get_hostname macro is expanded. The handling of this temporary is implicit; neither the caller nor the callee have to worry of allocating or deallocating it. On the calling side this convention is simple to use without having the callee expose a static buffer.

In P99, it is currently applied in a few places, in particular in the header file "p99_posix_default.h". Its use will probably grow in future releases.