Ease the transition between C standards

With this post I will start to discuss a series of modifications of the C standard that I have (or will) propose for C2x. As a starting point there is the observation that the passage from one C standard to the next  was not easy in the past. This, neither for implementers of C compilers nor for users, because it was not easy to capture partial improvements. Basically for gcc, e.g., you had to add -std=c11 or similar, and then test for the version number to see if a particular feature is implemented. This task then was even more complicated because some of the new features are language, and some are library.

This was tedious and error prone, and I think we should avoid such difficulties in the future.

So for the next version I propose to relax the constraints on C standard header files. I think we should add a feature test macro for each header that reflects the C version which is completely implemented by this header.

Most headers of the C standard are currently optional, either because they provide an optional feature such as threads or atomics, or because an environment may be free standing. Having feature test macros implies that we then can require that all headers are mandatory. If the feature is not supported by the platform, the only contents of such a header would be the feature test macro, set to 0.

It would be nice if the transition to the next standard could just start by this: everybody provide all standard headers, eventually empty but for the test macro.

For the interested you can find my proposal here:

Mandatory C library headers
simplify the transition to a new C standard

7 thoughts on “Ease the transition between C standards”

  1. Interesting proposal. A different way to do this would be to introduce different #include behavior. If we had a “#ifninclude ” that behaved much like #ifndef, couldn’t we do everything in your proposal without adding all the header files? It would also lend itself to handling includes with different names on different platforms. If the include file was not found, then we would do some other step.

    #define __STD_ATOMIC__ 0

    Or, doing the testing I was talking about:

    #define OOPS (1)

    The idea would be that if the include fails to find the file, we have a fallback. In the first case, we note that we do not have atomics. In the second, we try to use a different header file instead. Perhaps it could be extended to behave more like an #if statement or even be made to be part of extended #if syntax:

    #if include
    … do things with atomics …
    … do things without atomics …

    Just an idle thought I had…

  2. Hmm, I did not get an email from WordPress.

    Interesting. It is pretty much what I was suggesting, except fleshed out and actually viable 🙂 I hope that makes it into C. Thanks for the link!

  3. There are many situations where freestanding implementations may be implement some features of atomics without locking, but not all, and where usefully implementing any kind of blocking operations would be impossible. If an atomic uint64_t used locking, and an interrupt handler tries to access one while the main line or an outer-level interrupt holds the lock, deadlock would result. In a freestanding implementation that uses interrupts but not threads (extremely common), an atomic type using locks would kill the system in any case where a non-atomic type wouldn’t work. Not very helpful. Allowing compilers to specify that they support some atomic features but not all would be a major step in the right direction.

    More generally, I think the Standard should much more eagerly recognize and clearly define optional features and guarantees. Different kinds of programs have different needs. If some systems can cheaply provide feature/guarantee X and some can’t, and some programs would benefit from feature X and some wouldn’t, I would suggest that an inability to provide the feature should not preclude an implementation from running programs that don’t need it in Standard-defined fashion, but programs that will be used exclusively on systems that provide the feature should likewise be able to exploit it in Standard-defined fashion. An attempt to use a program that needs a feature or guarantee on a system that can’t support it should fail in Implementation-Defined fashion, preferably before any part of it begins execution.

    I think there has been a political aversion toward making “recommendations”, but I think that should be countered by observing that implementations will often have good reasons for not supporting features or guarantees that would be supported on others. Saying that implementations should offer a feature or guarantee absent a compelling reason to do otherwise, should hardly be seen as disparaging toward implementations where a compelling reason exists. The statement might be viewed as disparaging toward implementations that throw precedent out the window for no particular reason beyond the fact that the Standard allows them to do so, but such implementations should be disparaged.

    1. I am not sure what you are referring to concerning atomics. This is a place where very fine grained feature test macros exist. So a free standing environment that wants to support atomics may support most atomics just by stubs that are implemented with locks. For the ones that are lock free it then implements them lockfree and indicates this by means of the corresponding macros. Any user that is only interested in lockfree atomics can test for this and would only drag in the code for lockfree attomics. (All implementations of C11 atomics that I know of work like this. The lock part is not very difficult to implement and there are open source implementations that you can use.)

      1. Not all C implementations can support locks. Locks, like other aspects of threading, generally require OS support, while many freestanding implementations are designed to be OS-agnostic.

        Perhaps you are unfamiliar with how common embedded systems work, but the majority of them use interrupts rather than threads. If a program is executing a piece of code (the “main line”) and the system receives some stimulus (perhaps a timer expired or a character arrived on a serial port), the system will make a call to an interrupt handler function. The main-line will automatically resume execution when the interrupt handler returns, but there’s no way it can continue execution before that. In order for a system to usefully implement an atomic object (or do anything else) using locks, it must have a means by which code that’s holding a lock can run while code that’s waiting on it is blocked. If the code that’s holding the lock can’t run until the function that’s waiting on it completes, deadlock will result.

        If an implementation can’t support locks and it can’t do an operation without locks, does it make more sense to have the implementation claim to support atomic 64-bit operations using locks and then have requests for such operations produce machine code that can’t possibly work, or would it be better for such implementations to reject programs that would need such operations. I would suggest that an implementation that can’t support 64-bit atomic operations should reject any program that would require them, but the only way the Standard would allow that would be if it reported that it couldn’t handle any atomic operations at all.

        1. If there is no support for threads, locks in the sense that is needed here may just be spinlocks, just do nothing or crash the program. The only important thing is that the lock-free property for the type is set correctly. A program that needs 64 bit atomics just for communication with signal handlers can test for atomics, then for the lock-free property of the type and bail out with #error if needed.

Comments are closed.