Introduction
The previous article in this series on Autotools showed how you can use Autoconf macros to probe the platform to see if it supports certain library functions, e.g., fnmatch or getline. If not, Autotools will compile its own versions downloaded from the Gnulib portability library.
As C has evolved over the years with newer standards, e.g., C11 and C23, both new language features have been added to the language and new APIs have been added to the standard library. For every new standard, the value of the __STDC_VERSION__ preprocessor macro is updated, e.g., 201112L for C11 and 202311L for C23. (C++ uses __cplusplus for this.) Hence, one way to know whether you can use a certain language feature or library function is simply to check __STDC_VERSION__. For example, to check whether “no return” can be [[noreturn]] or _Noreturn:
#if __STC_VERSION__ >= 202311L
# define _Noreturn [[noreturn]]
#else
# /* Assume _Noreturn is fine as-is. */
#endif
While this often works, it has a few problems:
- Using
__STDC_VERSION__is a blunt instrument. In many cases, compiler vendors support language features as extensions long before the features are standardized. For example, gcc supportedtypeoflong before it made it into C23. - In some cases, the semantics of extensions may not match those in an eventual standard. Ideally, you want to probe the compiler in advance to see whether it supports a language feature in exactly the way you need.
- Using
#if __STDC_VERSION__works only in C source code; it doesn’t work in makefiles, so you can’t conditionally compile.cfiles based on the compiler’s standard version.
Much better than relying on __STDC_VERSION__ is to probe the compiler directly to get definitive answers as to whether it supports the features you need. Continuing in this series, this article is about using Autotools to probe the compiler and platform directly.
Probing the Compiler
Autotools has a built-in macro AC_COMPILE_IFELSE that can be used to probe the compiler. For example, to check whether typeof is supported:
AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([], [typeof(0) x;])]
[pjl_cv_typeof=yes],
[pjl_cv_typeof=no]
)
if test "x$pjl_cv_$1" = xyes; then
AC_DEFINE(HAVE_TYPEOF, [1], [Define to 1 if you have 'typeof'.])
else
AC_DEFINE(HAVE_TYPEOF, [0], [Define to 1 if you have 'typeof'.])
fi
AC_LANG_PROGRAM is used to specify a program fragment (where you don’t need to explicitly declare main yourself) to see if it compiles. The idea is to put the smallest possible fragment, often a single statement, that definitively determines whether a language feature is supported. For typeof, the declaration:
typeof(0) x; // If supported, same as: int x;
will compile only if typeof is supported.
Reminder: in C, the type of an unadorned integer literal is
int.
If the snippet compiles, then the variable pjl_cv_typeof is set to yes, otherwise no. Then, we can use a standard shell if statement to define HAVE_TYPEOF to either 0 or 1 that will be added to config.h.
Names of variables like
pjl_cv_typeofconventionally containcvfor “configure value.”
Writing m4 Macros
While the above works, it’s fairly verbose, especially if you want to probe the compiler for several language features. Better would be to write your own Autoconf m4 macro:
AC_DEFUN([TRY_COMPILE], [
AC_CACHE_CHECK([for $1], [pjl_cv_$1],
[AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([$2], [$3])],
[pjl_cv_$1=yes],
[pjl_cv_$1=no]
)]
)
if test "$pjl_cv_$1" = "yes"; then
AC_DEFINE(HAVE_[]m4_toupper($1), [1], [Define to 1 if you have '$1'.])
else
AC_DEFINE(HAVE_[]m4_toupper($1), [0], [Define to 1 if you have '$1'.])
fi
])
This macro takes three arguments:
- Its label.
- Preamble, if any, such as necessary
#includes. - Code snippet.
Arguments are referred to via $x where x is its number.
AC_DEFUN is used to define a new Autoconf macro taking its name and body as arguments.
AC_CACHE_CHECK does two things:
- When
configureis running, prints a line of the formcheckingwhatever...followed by eitheryesornowhere whatever is the first argument toAC_CACHE_CHECK. In this case, it would printchecking for typeof...which is nice since it lets the user know what’s going on and the result of the probe. - Caches the result in the variable
pjl_cv_$1where$1is first expanded.
M4 also has its own set of built-in macros, such as m4_toupper that converts its argument to uppercase, hence:
HAVE_[]m4_toupper($1)
will define HAVE_TYPEOF when typeof is $1. (The [] is needed to separate HAVE_ from m4_toupper otherwise it would have attempted to define HAVE_m4_toupper which isn’t what you want.)
If you put this macro into m4/try_compile.m4, you can use it in configure.ac:
TRY_COMPILE([typeof], [], [typeof(0) x;])
That’s a lot less verbose.
For another example, if you want to use C23’s nullptr in your program if the compiler supports it, you can put the following into configure.ac:
TRY_COMPILE([nullptr], [], [void *p = nullptr;])
if test "x$pjl_cv_nullptr" = xno; then
AC_DEFINE([nullptr], [NULL], [Define to NULL.])
fi
If the compiler doesn’t support it, configure will put the following into config.h so it’ll fall back to using NULL:
#define nullptr NULL
Of course
nullptrandNULLaren’t 100% equivalent, butNULLcan be substituted fornullptrin the majority of cases.
If you want to require a particular language feature, you can also write a MUST_COMPILE variation:
AC_DEFUN([MUST_COMPILE], [
AC_CACHE_CHECK([for $1], [pjl_cv_$1],
[AC_COMPILE_IFELSE(
[AC_LANG_PROGRAM([$2], [$3])],
[pjl_cv_$1=yes],
[pjl_cv_$1=no]
)]
)
if test "x$pjl_cv_$1" = xno; then
AC_MSG_ERROR(["$1" not supported by C compiler])
fi
])
where AC_MSG_ERROR prints an error message and terminates configure.
Preprocessor Macros in Makefiles
As mentioned, preprocessor macros can’t be used in makefiles. For example, if you wanted to compile my_threads.c only if __STDC_NO_THREADS__ is defined by the compiler, you could put the following into configure.ac:
TRY_COMPILE([__STDC_NO_THREADS__], [], [int x = __STDC_NO_THREADS__;])
AM_CONDITIONAL([HAVE___STDC_NO_THREADS__],
[test "x$pjl_cv___STDC_NO_THREADS__" = xyes])
Then in src/Makefile.am you can do:
my_prog_SOURCES = my_prog.c
if HAVE___STDC_NO_THREADS__
my_prog_SOURCES += my_threads.c
endif
Conclusion
Although __STDC_VERSION__ exists as a mechanism for checking what version of C the compiler supports, it’s a blunt instrument. Far better is to probe the compiler directly for features that you want.
There are more parts to come!
Top comments (0)