13.md

Building with a concrete C specification version

If you want to make sure your code is built following a concrete C version, it has two parts:

  • you must request the C specification version in your C code
  • you need an implementation (compiler, linker, ...) that supports the version you want

Limiting your code to the specific C spec version

In C89, there was no __STDC_VERSION__ macro (note there are two underscores both leading and terminating the macro). In the Normative Addendum 1 for C89 from 1994, the macro was defined with a value of 199409L. In the C99 specification (there is also a handy txt only version, the macro has a value of 199901L.

So, if you want your compiler to use a minimal or a specific C version, work with the macro and #ifdef and #error preprocessor directives.

#ifndef __STDC_VERSION__
#error "__STDC_VERSION__ not defined.  C99 compiler or greater required."
#else
#if __STDC_VERSION__ < 199901L
#error "C99 compiler or greater required."
#endif
#endif

👀 check-c-version.c

👀 request-c99.c

Compiler version

In the 1997: Single UNIX Specification (SUS) version 2, with systems supporting that version as UNIX98, the compiler binary supporting C89 had to be named c89. You can search SUS ver 2 here, and type c89.

In POSIX:2004, which is SUS ver 3 with updates, the C compiler binary supporting C99 standard had to be named c99. The latest SUS version (as of May, 2020) still defines c99. I expect the future SUS versions with require c11 binary to support C11 standard.

TODO: c89 on macOS, gcc -std=c99, c89 on macOS with _Bool

Might be difficult to get the compiler work as the exact specification version. For example, the following will compile while // are not part of C89, and moreover, _Bool is not either and it does not issue even a warning.

janp:air:~$ cat main.c
int
main(void)
{
	// not in C89
	_Bool b;
}
$ c89 main.c
main.c:4:2: warning: // comments are not allowed in this language [-Wcomment]
        // not in C89
        ^
1 warning generated.
$ gcc -std=c89 -pedantic main.c
main.c:4:2: warning: // comments are not allowed in this language [-Wcomment]
        // not in C89
        ^
1 warning generated.

Flexible array member

  • since C99 (§6.7.2.1, item 16, page 103)
  • it is an array without a dimension specified as the last member of the structure
    • handy for structures with a fixed header and some "padding" data of flexible length that is allocated dynamically
    • why not to use a pointer instead? It is good when passing data over boundaries such as network, kernel/userland, etc. since no structure copy is required.
      • just copy the structure as a whole (however, it is necessary to know how large is the padding) because it is all contiguous memory
  • sizeof (the_structure) gives you the size of the structure as if the flexible array was empty.

Example:

struct item {
	int value;
	// possible other members
	char payload[];
	// nothing can follow
};

sizeof (struct item) will give the size without the last member computed

+-------------+-----------------------+
| struct item |      payload ...      |
+-------------+-----------------------+

Previously, this was hacked around using array with 0 members and GCC accepted this. Since C99 this can be done properly using this technique.

The extra data is allocated as follows:

struct item *p = malloc(sizeof (struct item) + payload_len * sizeof (p->payload[0]));
  • with this approach the overall structure alignment might be lost
    • i.e. it is necessary to set the payload length according to the size of the structure if you want to maintain the alignment

👀 flexible-array-member.c

Structure bit fields

  • sometimes memory is scarce (imagine having to keep millions of big structures in memory) and there are members holding integer values that occupy just a couple of bytes
    • bit fields can be used to shrink the memory needed
struct foo {
	unsigned int a : 3;
	unsigned int b : 1;
	unsigned int   : 4;
};
  • the number after the colon specifies the number of bits for a given bit field

    • cannot exceed the size of the underlying data type (unsigned int in the above example)
  • you cannot use sizeof on a bit field

  • this is good for implementing network protocol headers or HW registers, however the layout of bit fields in a C structure is implementation dependant

    • if it needs to match a concrete layout, additional non-standard compiler features have to be used (#pragma's etc.)
    • there is no offsetof() for bit fields

👀 bitfield.c

  • the integer values will behave as expected, e.g.
struct foo_s {
	unsigned int a : 3;
} foo;

foo.a = 7;
foo.a++; // will wrap around to 0 since this is unsigned

👀 bitfield-wraparound.c

Non-transparent handles may cause issues

If your library is statelful, the state is often managed by a handle used as an argument in the library function calls. Let's say internally in the library we need to track some ID and a position. Those members are expected to be used only by the library itself so the consumers of the library should use the handle in a transparent way. That is, they should not care nor use whatever is inside the handle.

/* mylib.h */
struct ml_hndl {
	int ml_id;
	int ml_pos;
};

/* mylib.c */
struct handle *
ml_init(void)
{
	/* allocate and init the handle first */
	/* ... */

	return (h);
}

/* mylib.c continues */
enum ml_err
ml_parse(struct handle *h, char *url)
{
	enum ml_err e = ML_OK;

	/* parse the URI */
	/* ... */

	return (e);
}

/* ... */

And the consumer would use it as follows:

#include <mylib.h>

int
main(void)
{
	ml_err ret;
	struct ml_hndl *h;

	h = ml_init(void);
	if ((ret = ml_parse(h, "some URI")) != ML_OK)
		errx(1, "...");

	/* ... */
}

So far, so good. However, what if the consumer code delves into the handle and uses the information in there (because the header file is part of the library and available to the consumer)?

int
main(void)
{
	/* ... */

	print_debug("[Debug] handle ID: %d\n", h->md_id);

	/* ... */
}

This will work fine until the library provider changes the handle (believed to be internal to the library):

/* mylib.h */
struct ml_hndl {
	long long ml_id;	/* used to be an int */
	int ml_pos;
};

Unless the consumer code is recompiled after the change, which may not happen as it could be some 3rd party software bought by the customer years ago, the debug print is now incorrect as it only prints the first 4 bytes of the structure, not 8.

There are ways to mitigate that:

  • use something non-revealing as a handle, like in index to the internal array of structures that keep the states. That is possible but you now need to manage the internal structure that keeps the state structures. You also cannot change the index type unless you recompile all the consumers as well.
  • the library provider could use something called incomplete types and use them to provide an opaque (transparent) handle.

Incomplete types

An incomplete type is a type that describes an object but lacks information needed to determine its size. You cannot declare objects using such types as the lack of the size prevents to allocated memory for them on the stack or heap.

$ cat main.c
struct x a;	/* declaring a variable using a structure of an unknown size */

int
main(void)
{
}

$ cc main.c
main.c:1:10: error: tentative definition has type 'struct x' that is never
      completed
struct x a;
         ^
main.c:1:8: note: forward declaration of 'struct x'
struct x a;
       ^
1 error generated.

The x structure could be completed, for all declarations of that type, by declaring the same structure tag with its defining content later in the same scope. We could only forward declare the structure though (that is, no defining any object of that structure type).

As we do not need to know the size of the x structure, the following code compiles.

$ cat main.c
struct x;	/* this is called forward declaration */

int
main(void)
{
}

$ cc main.c
$ echo $?
0

BTW, the void type is an incomplete type that can be never completed. So, you cannot declare a variable of type void.

$ cat main.c
int
main(void)
{
	void x;
}

$ cc main.c
main.c:4:7: error: variable has incomplete type 'void'
        void x;
             ^
1 error generated.

However, you can always declare and work with a pointer to an incomplete type (all structures are always aligned to the same byte boundary). The following will compile and run fine.

#include <stdio.h>

struct x *
myfn(struct x *p)
{
        return (p);
}

int
main(void)
{
        myfn(NULL);
}

This feature of the C language can be used to represent opaque handles, and those can be used to solve the problem with non-transparent handles .

Opaque structures (handles)

With the help of incomplete types, it is possible to declare a structure in a header file along with API that consumes it without revealing what the structure actually contains. The reason to do that is not to allow a programmer to depend (= use) on the structure members, thus allowing to change the structure innards anytime.

/* Forward declaration in foo.h */
struct foo;

void
do_stuff(struct foo *f);

The file implementing do_stuff() will retype the opaque structure object to an internal structure type.

/* in foo_impl.h not provided to the consumers */
struct foo_impl {
    int x;
    int y;
};

/* library implementation code */
void
do_stuff(struct foo *f)
{
	struct foo_impl *fi = (struct foo_impl *)f;

	fi->x = ...
	fi->y = ...
}

Then any .c file that includes foo.h can work with the structure (by passing it to do_stuff()) however cannot access its members directly. The structure is usable only via a pointer. Thus, the library has to provide a function to allocate a structure and return the pointer to it (and possibly also to initialize it) and functions to get/set the data inside the structure.

This is handy for libraries so they are free to change the layout of structures without breaking the consumers.

The consumer of the library then #includes only foo.h but it does not have access to foo_impl.h.

#include <stdio.h>

#include "foo.h"

int
main(void)
{
	struct foo *h;

	h = getFoo();
	doStuff(h);
}

👁️