Home > Articles > Programming > C/C++

📄 Contents

  1. C++ Compatibility
  2. Generic Macros / Concurrency / Error Checking
  3. Quick Exit / Unicode / Compiler Support
  • Print
  • + Share This
From the author of Generic Macros

Generic Macros

One of the most hyped features of C11 is the _Generic() keyword for macros. This keyword kind of allows type-generic macros by letting you write a macro that selects between different implementations based on the types of their arguments. This capability was added for one very simple reason: to make it possible to implement the tgmath.h header when using standard C. This header is supposed to contain macros that expand to the correct math.h function, irrespective of their types. For example, cos(x) will call cos(), cosf(), cosl(), ccos(), ccosf(), or ccosl(), depending on whether the argument is a float, double, long double, _Complex float, and so on.

In Clang, this header is implemented using overloaded functions—a C++ feature made available as an extension in C mode. It's impossible to implement this header in purely standard C99 code, which is a somewhat embarrassing situation, as this header is mandated by the C99 specification. With C11, this cos macro can now be implemented as something like this:

#define cos(x) _Generic(x, 
      long double _Complex: ccosl(x),
      double _Complex: ccos(x),
      float _Complex: ccosf(x),
      long double: cosl(x),
      double: cos(x),
      float: cosf(x))

When you use this macro, the compiler will select the correct function to call based on the argument type. Unfortunately, beyond this single case, _Generic is simply not very useful. In most cases, tgmath.h is just a way of introducing bugs into your code due to accidental truncation: calling cosf() or cosl() explicitly ensures that these types of bugs don't happen.

Concurrency

It shouldn't come as a surprise that a lot of effort in the new C specification went into making it easier to write multithreaded code in C. The biggest change is that C now has a memory model that accepts the fact that variables may be modified from multiple threads. This comes with a set of explicit memory orderings and barriers, which I discussed in my earlier article "Understanding C11 and C++11 Atomics."

Interestingly, C11 comes with a set of standard functions for creating threads, as well as their basic synchronization primitives, which has been the cause of a lot of grumbling. The interfaces are semantically more or less equivalent to the POSIX threading APIs, but with some subtle differences.

C11 also includes some poor design choices, such as specifying timeouts in absolute times. This leads to some interesting problems when; for example, when the Network Time Protocol changes your clock time backward. In general, expiring from a timeout too early is mildly irritating, whereas a timeout that's never reached can cause code to lock up completely. With absolute timeouts, the C11 versions opt for the worse option.

The rationale for C11 threading support is twofold. First, since C is now designed to support concurrency, it would be a bit silly if the standard didn't provide any mechanism for actually spawning multiple threads. Second, the POSIX thread API isn't universal. The standards committee hopes that the C11 threading APIs will be implemented for embedded environments on non-POSIX systems. Other systems are expected to implement them on top of their native threading APIs.

The other thread-related change is the inclusion of the _Thread_local storage qualifier, which means that each thread gets a private copy of a variable. Unfortunately, as with the GCC version of this feature, it's incredibly difficult to use correctly. The POSIX threading APIs for thread-local storage allow you to register a cleanup function to avoid leaks when a thread exits. _Thread_local variables lack this capability; so, for example, they can't safely contain pointers to objects that need to be cleaned up when the thread exits.

Error Checking

One fairly common bug that you'll see in old C code is something like this:

#if sizeof(void*) > 4

The preprocessor will interpret sizeof() as an undefined macro, evaluating it to 0. Therefore, this #if block will never be reached, even on 64-bit platforms. There are lots of related errors where people try to do compile-time checking.

C11 adds a _Static_assert() keyword that lets you check compile-time conditions. This feature is especially valuable when writing portable code, as it lets you check the assumptions that you made and raise an error during compilation when someone tries to build in a platform where they're not valid.

The standard also contains a load of _s-suffixed versions of standard library functions. Many of these functions will be familiar to Windows programmers, as Microsoft has provided them for a while. They generally add explicit size parameters accompanying pointers to buffers.

  • + Share This
  • 🔖 Save To Your Account