C compilers: GCC and Clang
last modified November 14, 2026
GCC and Clang are the two C compilers that matter on Linux and macOS. Both
accept the -std option, both implement the same ISO standards, and
both can build the same programs. They do not, however, implement the newest
standards at the same time or to the same depth. This tutorial compares the two
compilers feature by feature and answers one question: which of them supports
the new C standards better? The short answer is GCC for the published C23
standard; the measurements, the remaining gaps, and the exceptions follow
below.
Two compilers, one language
GCC is the GNU Compiler Collection. Its C compiler is the cc1
front end, which the gcc driver runs before the assembler and the
linker. Clang is the C, C++ and Objective-C front end of the LLVM project, and
the clang driver does the same four steps: preprocess, compile,
assemble and link. Both emit ordinary object files and both link against the
same C library, which on this machine is glibc. That split matters when a
standard is evaluated: the compiler owns the language, while the C library owns
the headers and functions.
The language is selected with -std=. The strict names are
c17, c23 and, for the draft that follows C23,
c2y; the gnu17, gnu23 and
gnu2y names add GNU extensions. A program compiled without any
-std option uses the compiler's default dialect, and the default
is one of the biggest practical differences between the two compilers.
The examples below were run on GCC 13.2.0 and Clang 18.1.8, the versions
installed here. The current releases at the time of writing are GCC 16.2 and
Clang 23.1.5, and the section on current releases uses their official status
pages for the newer features that the installed compilers do not have. Note
that cc is not necessarily either of them: on this system the
cc command runs GCC 11.4, so the commands below name
gcc and clang explicitly.
The default C standard
The __STDC_VERSION__ macro reports the dialect in use:
201710L for C17 and 202311L for C23. The following
program prints it.
#include <stdio.h>
int main(void) {
#ifdef __STDC_VERSION__
printf("__STDC_VERSION__ = %ldL\n", __STDC_VERSION__);
#else
printf("__STDC_VERSION__ is not defined\n");
#endif
return 0;
}
Compiled with no options, both compilers report 201710L: the
default is C17 in each of these versions. GCC 13 needs the older spelling
-std=c2x, because the -std=c23 alias arrived in GCC
14; in that mode it reports 202000L, the placeholder value used
before ISO published C23. Clang 18 understands -std=c23 and
reports the final 202311L. Current GCC, from version 15, defaults to
gnu23, while Clang still defaults to gnu17.
$ gcc standards.c -o standards $ ./standards __STDC_VERSION__ = 201710L $ clang standards.c -o standards $ ./standards __STDC_VERSION__ = 201710L $ gcc -std=c2x standards.c -o standards $ ./standards __STDC_VERSION__ = 202000L $ gcc -std=c23 standards.c -o standards gcc: error: unrecognized command-line option ‘-std=c23’; did you mean ‘-std=c2x’? $ clang -std=c23 standards.c -o standards $ ./standards __STDC_VERSION__ = 202311L
C23 keywords
The most visible C23 change is that bool, true,
false, nullptr, static_assert,
alignas, alignof and thread_local are
keywords. In C17 they are macros or typedefs supplied by
<stdbool.h>, <stddef.h>,
<assert.h>, <stdalign.h> and
<threads.h>. This program uses the names without including
any of those headers.
#include <stdio.h>
int main(void) {
bool flag = true;
static_assert(sizeof(int) >= 2);
int *p = nullptr;
printf("flag = %d\n", flag);
printf("p is null: %d\n", p == nullptr);
return 0;
}
Both installed compilers accept the file in C23 mode and print the same
result. Clang has supported the keyword spellings since Clang 17, and GCC since
GCC 13. The one-argument form of static_assert is also new: C11
required a message string, C23 makes it optional.
$ gcc -std=c2x keywords.c -o keywords $ ./keywords flag = 1 p is null: 1 $ clang -std=c23 keywords.c -o keywords $ ./keywords flag = 1 p is null: 1
typeof, constexpr and auto
C23 added three declarations that used to be compiler extensions or C++ only:
typeof and typeof_unqual for the type of an
expression, constexpr for compile-time constants, and
auto for type inference. The next example uses all three.
#include <stdio.h>
int main(void) {
constexpr int size = 4;
typeof(size) n = size;
auto ratio = 2.5;
printf("n = %d\n", n);
printf("ratio = %g\n", ratio);
return 0;
}
GCC 13 implements all three and builds the file. Clang 18 understands
typeof (Clang 16) and auto (Clang 18) but not
constexpr, which arrived in Clang 19, so the declaration fails and
the two uses of size fail with it. Removing the
constexpr line makes Clang 18 accept the rest.
$ gcc -std=c2x inference.c -o inference
$ ./inference
n = 4
ratio = 2.5
$ clang -std=c23 inference.c -o inference
inference.c:4:5: error: use of undeclared identifier 'constexpr'
4 | constexpr int size = 4;
| ^
inference.c:5:12: error: use of undeclared identifier 'size'
5 | typeof(size) n = size;
| ^
inference.c:5:22: error: use of undeclared identifier 'size'
5 | typeof(size) n = size;
| ^
3 errors generated.
Bit-precise integers
_BitInt(N) declares an integer with exactly N bits,
including widths that are not a power of two. The next program stores 100 in
an unsigned seven-bit integer.
#include <stdio.h>
int main(void) {
unsigned _BitInt(7) x = 100;
printf("x = %u\n", (unsigned) x);
return 0;
}
The installed compilers disagree in the opposite direction this time: GCC 13
predates the feature and fails to parse the declaration, while Clang has
supported _BitInt since Clang 15 and builds and runs the program.
GCC added it in GCC 14, and only for IA-32, x86-64 and little-endian AArch64.
$ gcc -std=c2x bitint.c -o bitint
bitint.c: In function ‘main’:
bitint.c:4:22: error: expected declaration specifiers or ‘...’ before numeric constant
4 | unsigned _BitInt(7) x = 100;
| ^
bitint.c:6:35: error: ‘x’ undeclared (first use in this function)
6 | printf("x = %u\n", (unsigned) x);
| ^
bitint.c:6:35: note: each undeclared identifier is reported only once for each function it appears in
$ clang -std=c23 bitint.c -o bitint
$ ./bitint
x = 100
Embedding files
The #embed directive turns a file into an initializer at compile
time, without an external converter such as xxd. Here a five-byte
file holds the word Hello, and the program embeds it in a byte
array.
#include <stdio.h>
int main(void) {
static const unsigned char data[] = {
#embed "data.bin"
};
printf("%zu bytes, first byte %d\n", sizeof data, data[0]);
return 0;
}
Both installed compilers reject the directive, and for the same reason: the
feature is newer than they are. GCC implements #embed in GCC 15
and Clang in Clang 19. The diagnostic wording differs, which is typical of the
two front ends.
$ printf 'Hello' > data.bin
$ gcc -std=c2x embed.c -o embed
embed.c: In function ‘main’:
embed.c:5:2: error: invalid preprocessing directive #embed
5 | #embed "data.bin"
| ^~~~~
$ clang -std=c23 embed.c -o embed
embed.c:5:2: error: invalid preprocessing directive
5 | #embed "data.bin"
| ^
1 error generated.
Attributes from C23
C23 attributes use the double-bracket syntax of C++.
[[unsequenced]] and [[reproducible]] describe
functions without side effects and without dependence on global state. The next
program marks a pure function and calls it.
#include <stdio.h>
[[unsequenced]] int square(int x) { return x * x; }
int main(void) {
printf("square(7) = %d\n", square(7));
return 0;
}
Neither installed version knows the attribute, but unknown attributes are only
a warning by default, so both programs still build and print 49. GCC 13 says
the attribute was ignored; Clang 18 calls it unknown. With
-Werror both stop with an error, and GCC 15 is the first GCC that
implements the attribute. Clang's status page still lists unsequenced functions
as not implemented, so even Clang 23 warns about this file.
$ gcc -std=c2x -Wall attrs.c -o attrs
$ ./attrs
attrs.c:3:1: warning: ‘unsequenced’ attribute ignored [-Wattributes]
3 | [[unsequenced]] int square(int x) { return x * x; }
| ^
square(7) = 49
$ clang -std=c23 -Wall attrs.c -o attrs
$ ./attrs
attrs.c:3:3: warning: unknown attribute 'unsequenced' ignored [-Wunknown-attributes]
3 | [[unsequenced]] int square(int x) { return x * x; }
| ^~~~~~~~~~~
1 warning generated.
square(7) = 49
Feature test macros
A portable program asks the preprocessor what the compiler supports instead of
guessing from its name. __has_c_attribute tests an attribute and
__STDC_VERSION__ tests the dialect. The version is an ordinary macro,
but __has_c_attribute is an operator that may only appear in a
preprocessing directive, so both results are stored in macros.
#include <stdio.h>
#if defined(__has_c_attribute)
# if __has_c_attribute(unsequenced)
# define HAVE_UNSEQUENCED 1
# else
# define HAVE_UNSEQUENCED 0
# endif
# define HAVE_ATTRIBUTE_QUERY 1
#else
# define HAVE_UNSEQUENCED 0
# define HAVE_ATTRIBUTE_QUERY 0
#endif
int main(void) {
printf("__STDC_VERSION__ = %ldL\n", __STDC_VERSION__);
printf("__has_c_attribute = %d\n", HAVE_ATTRIBUTE_QUERY);
printf("[[unsequenced]] = %d\n", HAVE_UNSEQUENCED);
return 0;
}
Both compilers have __has_c_attribute in both dialect modes, and
both answer 0 for unsequenced in these versions.
On GCC 15 the last line changes to 1, because GCC implements the attribute;
Clang 23 still answers 0.
$ gcc -std=c2x attributes.c -o attributes $ ./attributes __STDC_VERSION__ = 202000L __has_c_attribute = 1 [[unsequenced]] = 0 $ clang -std=c23 attributes.c -o attributes $ ./attributes __STDC_VERSION__ = 202311L __has_c_attribute = 1 [[unsequenced]] = 0
What current releases support
The installed compilers are well behind the current releases, so the comparison above shows the past. The current releases are GCC 16.2 and Clang 23.1.5, and their status pages give the minimum version for each C23 feature. The table merges both pages for the features that differ.
| Feature | GCC | Clang |
|---|---|---|
C23 keywords and nullptr | 13 | 17 |
typeof, typeof_unqual | 13 | 16 |
constexpr object definitions | 13 | 19 |
auto type inference | 13 | 18 |
_BitInt(N) | 14 | 15 |
#embed | 15 | 19 |
[[unsequenced]], [[reproducible]] | 15 | not implemented |
| Storage class specifiers in compound literals | 13 | not implemented |
| Decimal floating-point types | 4.3 | not implemented |
| Improved tag compatibility | 14 | partial since 21 |
Default -std | gnu23 since 15 | gnu17 |
| C23 overall | supported | partial |
The decimal floating types and the interchange types of Annex H are optional in
C23. GCC implements them on IA-32 and x86-64 since GCC 4.3 and on AArch64 since
GCC 13; Clang does not implement them at all, and its Clang 23 release notes
still say so when they diagnose the H, D and
DD length modifiers. _BitInt is a core feature, but
GCC 14 implements it only on IA-32, x86-64 and little-endian AArch64, while
Clang has supported it since Clang 15. Everything else in the table is from the
two official status pages, as of November 14, 2026.
C2y, the next standard
C2y is the working name of the revision after C23. It is not published, and
both compilers select it with -std=c2y: GCC 15 was the first GCC
release with the option, Clang 19 the first Clang, and the installed Clang 18
still rejects the name. Support in both is experimental and incomplete, so a
program that relies on it must pin the compiler version. Many of the features
discussed in WG14 are small clean-ups of the language, and both projects
implement a different subset.
GCC 15 added if declarations, which declare a variable in the
condition, and named loops. GCC 16 added _Countof and static
assertions in expressions. Clang 21 added _Countof, Clang 22 named
loops, and if declarations are listed for Clang 24. Clang also
implements several smaller papers, such as delimited escape sequences in
Clang 21 and __COUNTER__ in Clang 22, while both still list some
papers as unimplemented. For an unfinished standard the ranking is therefore
unstable, and the useful test is not a compiler name but
__STDC_VERSION__ plus a build of the actual feature.
Which compiler supports the new standards better?
For the published standard, GCC. Three facts decide it. First, C23 is complete
in GCC: every language feature in the standard has an entry on the GCC status
page and none is marked unimplemented, while Clang's page still labels C23
"partial" and lists missing features, including
[[unsequenced]], storage class specifiers in compound literals,
decimal floating types and Annex H. Second, GCC 15 changed the default dialect
to gnu23, so C23 code builds without any flags, whereas Clang still
defaults to gnu17 and rejects the same code unless
-std=c23 is added. Third, GCC's remaining C23 limitation is
target-specific rather than missing: _BitInt and decimal types
exist only on some architectures in older releases.
Clang is not behind in every area. It implemented _BitInt a
release before GCC, it has the better diagnostics, and its sanitizers,
clang-tidy, clangd and coverage tools are stronger
than the GCC equivalents. It also implements many C2y papers early, so a
project that tracks the draft may find a feature in Clang first. For code that
targets ISO C23, however, GCC is the compiler that accepts more of the
standard, with fewer flags and fewer warnings.
Building with both compilers
The practical answer is to build with both and let the stronger standard
support catch the mistakes. The script below compiles one source file with each
compiler using the newest spelling the compiler understands, with warnings
treated as errors. The probe is needed because GCC 13 does not know
-std=c23; on current compilers the fallback never runs.
#!/bin/sh
# build one source file with both compilers
for cc in gcc clang; do
if ! "$cc" -std=c23 -Wall -Wextra -Werror -o probe "$1" 2>/dev/null; then
"$cc" -std=c2x -Wall -Wextra -Werror -o probe "$1" || exit 1
fi
printf '%-6s' "$cc"
./probe
done
rm -f probe
Run against the first program, the script reports the dialect each compiler
selected. Adding -pedantic-errors turns every GNU extension into
an error as well, which is what a project that claims ISO conformance wants.
The compiler output is hidden for the probe only; in a real build it should be
visible, and a CI job should run the same matrix so that the older and the
newer release are both tested.
$ ./check.sh standards.c gcc __STDC_VERSION__ = 202000L clang __STDC_VERSION__ = 202311L
Source
C Standards Support in GCC
C Support in Clang
GCC 15 Release Notes: C23 by default
Clang 23.1.0 Release Notes
cppreference: C23
This tutorial compared GCC and Clang on the C standards, from the default
dialect and the C23 keywords to _BitInt, #embed and
the C2y draft, and found GCC ahead for the published C23. The examples are
standard C, so any conforming compiler accepts them once it implements the
feature under test; for the library functions used in them see the
C strlen function and the
C printf function tutorials.
Author
List C Standard Library.