Introduction

If all you have is a hammer, everything looks like a nail. If your hammer is C++, everything looks like your thumb.

I don't know who said it first, but this humorous perversion of the law of the instrument has, in several variations, been floating around the internet for ages. While it is probably more true of C++, the same can certainly be said of C.

Both C and C++ give the programmer a huge amount of control over what the computer actually does. While this does make it relatively easy to write efficient code, it also makes it relatively easy to write code which crashes. Higher level lanuages can often provide descriptive error messages at run-time, but with C/C++ there is often a single run-time error message: Segmentation fault (core dumped), and that's if you get lucky enough to actually see an error message! Often, the program can keep running but generate data which is complete garbage or, even worse, data which is just slightly incorrect (which is much harder to spot).

To make up for the lack of run-time errors, compilers and static analyzers instead try to generate compile-time errors. With a few simple annotations, you can give compilers and static analyzers more information about how your program is supposed to work, which gives them have a better chance of informing you when you, or someone using your API, make a mistake.

Of course, it's not all about the errors… giving the compiler more information about your program also means it can do a better job of optimizing for you. Unfortunately, different compilers can handle different information, often presented in different ways, which can lead to an #ifdef maze or, more often, simply omitting the extra information.

Hedley helps by providing easy-to-use macros which hide that #ifdef maze away in a single C/C++ header which works everywhere; just drop it into your project and #include it—no build system magic is required.

For example, here is what a function to print a fatal error might look like using Hedley:

HEDLEY_NO_RETURN
HEDLEY_NON_NULL(1,2)
HEDLEY_PRINTF_FORMAT(2,3)
void print_fatal_error(FILE* fp, const char* fmt, ...);

Without Hedley, the same declaration would look like this:

#if defined(__IAR_SYSTEMS_ICC__) && (__VER__ >= 8000000)
__noreturn
#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
_Noreturn
#elif defined(__cplusplus) && (__cplusplus >= 201103L)
[[noreturn]]
#elif defined(__has_attribute)
#  if __has_attribute(noreturn)
__attribute__((__noreturn__))
#  endif
#elif \
  (defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 2)))) || \
  (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600)) || \
  (defined(__SUNPRO_C) && (__SUNPRO_C >= 0x5110)) || \
  (defined(__SUNPRO_CC) && (__SUNPRO_CC >= 0x5110)) || \
  ((defined(__CC_ARM) && defined(__ARMCC_VERSION)) && (__ARMCC_VERSION >= 4010000)) || \
  (defined(__xlC__) && (__xlC__ >= 0x0a01)) || \
  (defined(__TI_COMPILER_VERSION__) && (__TI_COMPILER_VERSION__ >= 8000000)) || \
  (defined(__TI_COMPILER_VERSION__) && (__TI_COMPILER_VERSION__ >= 7003000) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
__attribute__((__noreturn__))
#elif defined(_MSC_VER) && (_MSC_VER >= 1310)
__declspec(noreturn)
#elif defined(__TI_COMPILER_VERSION__) && (__TI_COMPILER_VERSION__ >= 6000000) && defined(__cplusplus)
#  pragma FUNC_NEVER_RETURNS;
#endif
#if defined(__has_attribute)
#  if __has_attribute(nonnull)
__attribute__((__nonnull__(1,2)))
#  endif
#elif \
  (defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)))) || \
  (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600)) || \
  ((defined(__CC_ARM) && defined(__ARMCC_VERSION)) && (__ARMCC_VERSION >= 4010000)) || \
__attribute__((__nonnull__(1,2)))
#endif
#if defined(__has_attribute)
#  if __has_attribute(format)
#    if defined(__MINGW32__)
#      if !defined(__USE_MINGW_ANSI_STDIO)
__attribute__((__format__(ms_printf, 2, 3)))
#      else
__attribute__((__format__(gnu_printf, 2, 3)))
#      endif
#    else
__attribute__((__format__(__printf__, 2, 3)))
#    endif
#  endif
#elif \
  (defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))) || \
  (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1600)) || \
  ((defined(__CC_ARM) && defined(__ARMCC_VERSION)) && (__ARMCC_VERSION >= 5060000)) || \
  (defined(__xlC__) && (__xlC__ >= 0x0a01)) || \
  (defined(__TI_COMPILER_VERSION__) && (__TI_COMPILER_VERSION__ >= 8000000)) || \
  (defined(__TI_COMPILER_VERSION__) && (__TI_COMPILER_VERSION__ >= 7003000) && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))
__attribute__((__format__(__printf__, 2, 3)))
#endif
void print_fatal_error(FILE* fp, const char* fmt, ...);

Not only is the Hedley version vastly easier to write, but it's easy enough to read that it helps document your code, meaning it's useful even if your compiler doesn't support the annotations in question.

Of course, you're unlikely to encounter that monstrosity above in the real world. You're much more likely to see something like:

#if defined(__GNUC__)
__attribute__(noreturn)
__attribute__((__format__(printf, 2, 3)))
__attribute__((__nonnull__(1,2)))
#elif defined(_MSC_VER)
__declspec(noreturn)
#endif
void print_fatal_error(FILE* fp, const char* fmt, ...);

While easier on the eyes than the first version (though not the Hedley version), there are a number of problems with this:

With Hedley, other people have already put in a lot of effort to make sure the compiler-specific annotations are written wherever they are accepted, but are not written when they would cause an error.

Getting Started

Hedley is a single header file; to use it, just include it:

#include "hedley.h"

And you're ready to go!

You can copy the header to your project if you want (it's CC0-licensed), or use a git submodule. You don't need to change the prefix ("HEDLEY_") or name ("hedley.h"), but if you want to feel free. I would appreciate it if you keep the link back to the original project, though, to help people keep any improvements synced (in both directions).

First Annotation: HEDLEY_NON_NULL

First, let's look at a pretty straightforward function:

#include "hedley.h"
#include <stdio.h>

struct Context {
	char* name;
};

void
print_message(struct Context* ctx, const char* message) {
	printf("%s: %s\n", ctx->name, message);
}

Here we just print out a message, but this is a very common pattern for lots of things: we take a pointer to a context, plus some additional argument(s), and do something with the fields of the context and the arguments. You see this everywhere.

Now, what happens if we call it with NULL for the context?

$ gcc -g -Wall -Werror test.c
$ ./a.out
Segmentation fault (core dumped)

Now, let's add a HEDLEY_NON_NULL attribute. HEDLEY_NON_NULL lists parameters which must never be NULL. It accepts a variadic list of parameters identified by their position (so in our example ctx is 1 and message is 2). While many C libraries can handle a NULL value for a string coversion specifier (%s), the standard doesn't mandate it, so we should make sure both parameters are non-NULL:

#include "hedley.h"
#include <stdio.h>

struct Context {
	char* name;
};

HEDLEY_NON_NULL(1,2)
void
print_message(struct Context* ctx, const char* message) {
	printf("%s: %s\n", ctx->name, message);
}

Let's see what happens when we try to compile and run our call now:

$ gcc -g -Wall -Werror test.c
test.c: In function ‘main’:
test.c:16:3: error: null argument where non-null required (argument 1) [-Werror=nonnull]
	print_message(NULL, "Hello, world!");
	^~~~~~~~~~~~~
cc1: all warnings being treated as errors

Most compilers aren't too smart about this; you can “trick” them by assigning NULL to a variable then calling the function, but it's definitely better than nothing. Static analyzers, on the other hand, can find extremely convoluted cases where you may be passing NULL.

Of course, HEDLEY_NON_NULL is far from the only macro Hedley supports. See the API Reference for the full list and lots of examples. There are macros for:

Static Analysis

Describe your API to the compiler and/or static analyzers, giving your users helpful warnings when they misuse your API, without false positives. Includes macros for:

Optimizations

Tell the compiler what you're doing (or what you want it to do) so compiled code is faster.

Managing Public APIs

Macros which help convey versioning information, when symbols are are added and deprecated, and symbol visibility.

Platform Detection

Macros Hedley uses to detect compiler, compiler version, and features availability.

Miscellaneous

Assorted macros which are useful, but don't really fit anywhere else.

Public APIs

Hedley includes some useful helpers for public APIs, but they aren't quite as simple to use as the rest of the macros. There are two groups of macros in this category: symbol visibility and versioning.

Visibility

With standard C, you can basically choose between “static” functions, which are visible only within the current compilation unit (i.e. the current file). This means that if you want to use a symbol in another compilation unit within your project, you have to expose it publicly. Even if you don't include a prototype in the public header, this still has implications for performance and code size. Furthermore, leaking internal sybmols which are not properly namespaced can result in collisions. For a good overview, see GCC's “Visibility” wiki page.

Hedley's visibility support is pretty easy to use, but it does require you to put some code in your header:

#if defined(FOO_COMPILATION)
  #define FOO_API HEDLEY_PUBLIC
#else
  #define FOO_API HEDLEY_IMPORT
#endif

Obviously, you should replace FOO_ with the appropriate prefix for your library.

Once you've included this code in one of your headers, simply add -DFOO_COMPILATION (or whatever your compiler wants) to the C flags you use to build your library, but not in the C flags people use to build with your library.

Then, simply annotate your public prototypes with FOO_API:

FOO_API
void
foo_bar(int baz);

If you compile with -fvisibility=hidden, only symbols annotated with FOO_API will be visible. If you'd like to avoid adding the visibility flag but still want the same effect, just annotate your internal (but non-static) functions with HEDLEY_PRIVATE; for static functions there is no need to add anything.

If you're using CMake ≥ 2.8.12, you can use the C_VISIBILITY_PRESET to set the proper visibility flag (if your compiler supports it).

Versioning

Hedley's versioning macros are a bit more complicated to use, but the effect can be pretty amazing. IMHO they are well worth the effort.

First off, credit where credit is due: the idea for these macros came from the GLib, where something very similar was implemented by Emmanuale Bassi.

First, you'll want to define some macros to identify the current version of the library. Usually this will be in a file which is automatically generated by the build system (see configure_file for CMake, or AC_CONFIG_FILES for autoconf), but you can maintain the file manually if you feel more comfortable with that. Here is an example for a library called “Foo”, version 1.2.3:

#include "hedley/hedley.h"

/* Define these to whatever your version number is. */
#define FOO_VERSION_MAJOR 1
#define FOO_VERSION_MINOR 2
#define FOO_VERSION_REVISION 3

#define FOO_VERSION HEDLEY_ENCODE_VERSION(FOO_VERSION_MAJOR, FOO_VERSION_MINOR, FOO_VERSION_REVISION)

Pretty standard stuff. Now, things start to get interesting; we want to let consumers target a specific version of your library, warning them if they use a symbol which is only available in a newer version, or is deprecated in the version they chose. We'll make the default target the current version:

#if !defined(FOO_TARGET_VERSION)
  #define FOO_TARGET_VERSION FOO_VERSION
#endif

Now, we'll define some macros for each version of your API:

#define FOO_VERSION_1_0 HEDLEY_ENCODE_VERSION(1,0,0)
#define FOO_VERSION_1_1 HEDLEY_ENCODE_VERSION(1,1,0)
#define FOO_VERSION_1_2 HEDLEY_ENCODE_VERSION(1,2,0)

#if FOO_TARGET_VERSION < HEDLEY_ENCODE_VERSION(1,1,0)
  #define FOO_AVAILABLE_SINCE_1_1 HEDLEY_UNAVAILABLE(1.1)
  #define FOO_DEPRECATED_SINCE_1_1
  #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement)
#else
  #define FOO_AVAILABLE_SINCE_1_1
  #define FOO_DEPRECATED_SINCE_1_1 HEDLEY_DEPRECATED(1.1)
  #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement) HEDLEY_DEPRECATED_FOR(1.1, replacement)
#endif

#if FOO_TARGET_VERSION < HEDLEY_ENCODE_VERSION(1,2,0)
  #define FOO_AVAILABLE_SINCE_1_2 HEDLEY_UNAVAILABLE(1.2)
  #define FOO_DEPRECATED_SINCE_1_2
  #define FOO_DEPRECATED_SINCE_1_2_FOR(replacement)
#else
  #define FOO_AVAILABLE_SINCE_1_2
  #define FOO_DEPRECATED_SINCE_1_2 HEDLEY_DEPRECATED(1.2)
  #define FOO_DEPRECATED_SINCE_1_2_FOR(replacement) HEDLEY_DEPRECATED_FOR(1.2, replacement)
#endif

Technically, the first three defines are unnecessary, but they can be convenient to allow people to define the target version.

Once we have these macros defined, we can annotate our API:

void
foo_bar(void);

FOO_DEPRECATED_SINCE_1_1_FOR(foo_qux)
void
foo_baz(void);

FOO_AVAILABLE_SINCE_1_2
void
foo_qux(void);

Now, let's see what happens when we try to call each function, while targeting different versions of the API:

$ cc -DFOO_TARGET_VERSION=FOO_VERSION_1_0 test.c
test.c: In function ‘main’:
test.c:7:3: warning: call to ‘foo_qux’ declared with attribute warning: Not available until 1.2
   foo_qux();
   ^~~~~~~~~~~~~~~~~~~~~~~~~
$ cc -DFOO_TARGET_VERSION=FOO_VERSION_1_1 test.c
test.c: In function ‘main’:
test.c:6:3: warning:foo_baz’ is deprecated: Since 1.1; use foo_qux [-Wdeprecated-declarations]
   foo_baz();
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test.c:1:0:
test.h:45:1: note: declared here
 foo_baz(void) {
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
test.c:7:3: warning: call to ‘foo_qux’ declared with attribute warning: Not available until 1.2
   foo_qux();
   ^~~~~~~~~~~~~~~~~~~~~~~~~
$ cc -DFOO_TARGET_VERSION=FOO_VERSION_1_2 test.c
test.c: In function ‘main’:
test.c:6:3: warning:foo_baz’ is deprecated: Since 1.1; use foo_qux [-Wdeprecated-declarations]
   foo_baz();
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test.c:1:0:
test.h:45:1: note: declared here
 foo_baz(void) {
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
$ cc test.c
test.c: In function ‘main’:
test.c:6:3: warning:foo_baz’ is deprecated: Since 1.1; use foo_qux [-Wdeprecated-declarations]
   foo_baz();
   ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
In file included from test.c:1:0:
test.h:45:1: note: declared here
 foo_baz(void) {
 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

This allows people depending on your API to avoid unintentionally bumping the version of your library they depend on when developing on a system with a newer version.

If you need to call the deprecated functions in your code (e.g., because one deprecated function is implemented by calling another, you can reuse the FOO_COMPILATION macro we defined earlier:

#define FOO_VERSION_1_0 HEDLEY_ENCODE_VERSION(1,0,0)
#define FOO_VERSION_1_1 HEDLEY_ENCODE_VERSION(1,1,0)
#define FOO_VERSION_1_2 HEDLEY_ENCODE_VERSION(1,2,0)

#if defined(FOO_COMPILATION)
  #define FOO_AVAILABLE_SINCE_1_1
  #define FOO_DEPRECATED_SINCE_1_1
  #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement)
#else
  #if FOO_TARGET_VERSION < HEDLEY_ENCODE_VERSION(1,1,0)
    #define FOO_AVAILABLE_SINCE_1_1 HEDLEY_UNAVAILABLE(1.1)
    #define FOO_DEPRECATED_SINCE_1_1
    #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement)
  #else
    #define FOO_AVAILABLE_SINCE_1_1
    #define FOO_DEPRECATED_SINCE_1_1 HEDLEY_DEPRECATED(1.1)
    #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement) HEDLEY_DEPRECATED_FOR(1.1, replacement)
  #endif
#endif

If you would like to take it a step further, you can create macros for the minimum required and max allowed versions. This allows people to target a range of different versions of your library (using #ifdefs to switch between different functionality based on the value of FOO_VERSION). For example:

#define FOO_VERSION_1_0 HEDLEY_ENCODE_VERSION(1,0,0)
#define FOO_VERSION_1_1 HEDLEY_ENCODE_VERSION(1,1,0)
#define FOO_VERSION_1_2 HEDLEY_ENCODE_VERSION(1,2,0)

#if !defined(FOO_TARGET_VERSION)
  #define FOO_TARGET_VERSION FOO_VERSION
#endif
#if !defined(FOO_VERSION_MIN_REQUIRED)
  #define FOO_VERSION_MIN_REQUIRED FOO_TARGET_VERSION
#endif
#if !defined(FOO_VERSION_MAX_ALLOWED)
  #define FOO_VERSION_MAX_ALLOWED FOO_TARGET_VERSION
#endif

#if FOO_VERSION_MIN_REQUIRED > FOO_VERSION_1_1
  #define FOO_DEPRECATED_SINCE_1_1 HEDLEY_DEPRECATED(1.1)
  #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement) HEDLEY_DEPRECATED_FOR(1.1, replacement)
#else
  #define FOO_DEPRECATED_SINCE_1_1
  #define FOO_DEPRECATED_SINCE_1_1_FOR(replacement)
#endif

#if FOO_VERSION_MAX_ALLOWED < FOO_VERSION_1_1
  #define FOO_AVAILABLE_SINCE_1_1 HEDLEY_UNAVAILABLE(1.1)
#else
  #define FOO_AVAILABLE_SINCE_1_1
#endif

#if FOO_VERSION_MIN_REQUIRED > FOO_VERSION_1_2
  #define FOO_DEPRECATED_SINCE_1_2 HEDLEY_DEPRECATED(1.2)
  #define FOO_DEPRECATED_SINCE_1_2_FOR(replacement) HEDLEY_DEPRECATED_FOR(1.2, replacement)
#else
  #define FOO_DEPRECATED_SINCE_1_2
  #define FOO_DEPRECATED_SINCE_1_2_FOR(replacement)
#endif

#if FOO_VERSION_MAX_ALLOWED < FOO_VERSION_1_2
  #define FOO_AVAILABLE_SINCE_1_2 HEDLEY_UNAVAILABLE(1.2)
#else
  #define FOO_AVAILABLE_SINCE_1_2
#endif

Notice that we still allow the consumer to simply define FOO_TARGET_VERSION; many consumers will not need the flexibility of setting the minimum required and maximum allowed versions separately.

One advantage of this method is that, if you need to call deprecated functions in your code, you can simply set FOO_VERSION_MIN_REQUIRED and FOO_VERSION_MAX_ALLOWED to 0 and FOO_VERSION in your code, and you don't have to have a special case for FOO_COMPILATION.