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 supportedtypeof
long 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.c
files 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_typeof
conventionally containcv
for “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
#include
s. - 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
configure
is running, prints a line of the formchecking
whatever...
followed by eitheryes
orno
where 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_$1
where$1
is 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
nullptr
andNULL
aren’t 100% equivalent, butNULL
can be substituted fornullptr
in 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)