Tooling[tools]
Clang vs GCC: what differs for a C++ project
- Section
- Tooling [tools]
- Standard
- C++23
- Compilers
- MSVC 19.44, Clang 19.1 (MSVC STL), GCC 16.2, Clang 23.1 (libc++)
- Published
- Length
- 14 min read
In this answer9 clauses
- 1Clang vs GCC at a glance
- 2What is the same: language, ABI and most flags
- 3The default standard: C++20 on GCC 16, C++17 on Clang 23.1
- 4libstdc++ or libc++: the standard library is the bigger difference
- 5C++20 to C++26: what each compiler has
- 6Diagnostics: one mistake, two messages, and warnings only one has
- 7Sanitizers, tools and licenses
- 8Speed of the compiler and of the code it builds
- 9Which to choose, and how to build with both
Abstract
On Linux, GCC and Clang compile the same C++ to the same Itanium ABI, take nearly the same flags and by default share libstdc++, so most projects build with both. They differ in the standard assumed without -std (GCC 16: C++20; Clang 23.1: C++17), in what libstdc++ and libc++ provide, in C++26 features (GCC 16 has reflection and contracts, Clang 23.1 neither) and in their warnings. Pass -std explicitly, use one standard library per program, and build with both in CI.

Clang vs GCC, for C++ on Linux: both compile the same language to the same Itanium C++ ABI, take nearly the same flags and, unless Clang is given -stdlib=libc++, use GCC’s libstdc++, so most projects build with both. They differ in the standard assumed without -std (C++20 on GCC 16, C++17 on Clang 23.1), in what libstdc++ and libc++ provide, in C++26 features and in warnings. Pass -std explicitly, keep one standard library per program, and build with both in CI: each warns about mistakes the other misses.
The samples were built with GCC 16.2 on libstdc++ 16 and Clang 23.1 on libc++ 23 (Linux x86-64, Compiler Explorer), Clang 23.1 again on libstdc++ 15 (its default there) and 16 (with --gcc-toolchain=), and on Windows MSVC 19.44 and the Visual Studio Clang 19.1, both on Microsoft’s STL. They are C++23 (-std=c++23 -Wall -Wextra; /W4 on MSVC) unless a section names another standard or no flag. The newest releases are GCC 16.2 (2026-08-07) and Clang 23.1.5 (2026-11-03); Since When has each feature’s first release.
Clang vs GCC at a glance
| What differs | GCC 16.2 | Clang 23.1 |
|---|---|---|
| Project and license | GNU; GPLv3+, runtime libraries with the Runtime Library Exception | LLVM; Apache 2.0 with LLVM exceptions |
Standard with no -std |
C++20 (gnu++20) |
C++17 (gnu++17) on Linux |
| Standard library on Linux | libstdc++; -stdlib= only in a g++ configured for it |
libstdc++, or libc++ with -stdlib=libc++ |
| C++26 language only it has | reflection (-freflection), contracts |
ordering of fold-expression constraints (P2963R3) |
| Library features only it has | libstdc++ 16: std::generator, <stacktrace>, std::inplace_vector |
libc++ 23: constexpr std::map, std:: |
| Warnings only it has | -Wduplicated-cond, -Wuseless-cast |
-Wthread-safety, -Wunsafe-buffer-usage |
| Sanitizers | address, undefined, thread | address, undefined, thread, memory |
| Static analysis | -fanalyzer, “only suitable for use on C code” |
the Clang Static Analyzer (scan-build); clang-tidy |
| Coverage | gcov | llvm-cov; gcov-compatible --coverage |
| Debugger | GDB, a separate GNU project | lldb |
| Also from the project | libstdc++ | libc++, lld, clang-format, clangd |
| Platforms covered here | Linux; Windows with MinGW-w64 | Linux; Apple’s own build; Windows, also as clang-cl |
What is the same: language, ABI and most flags
Both implement the Itanium C++ ABI on Linux, which “gives precise rules for implementing the language” and is “implemented in many major C++ compilers, including GCC and Clang” (Itanium C++ ABI). Its stated aim is to let users “build relocatable objects with different compilers and link them together” (no such link was run here); what those objects may pass to each other depends on the standard library and its release.
Clang also introduces itself as GCC: -fgnuc-version= sets __GNUC__ “to claim compatibility with the given GCC version (default is 4.2.1)”, and GCC’s manual says a defined __GNUC__ means GCC “or a non-GCC compiler that claims to accept the GNU C dialects”. Test __clang__ first:
// Clang also defines __GNUC__ or _MSC_VER: test __clang__ first.
#if defined(__clang__)
constexpr const char* compiler = "Clang";
#elif defined(__GNUC__)
constexpr const char* compiler = "GCC";
#elif defined(_MSC_VER)
constexpr const char* compiler = "MSVC";
#else
constexpr const char* compiler = "something else";
#endif— end example]
Clang 23.1 on Linux printed:
compiler Clang
__clang__ 1
__clang_major__ 23
__GNUC__ 4
__GNUC_MINOR__ 2
__GNUG__ 4
_MSC_VER -— end example]
GCC 16.2 printed __GNUC__ 16. The Visual Studio Clang 19.1 defined _MSC_VER 1944 and no __GNUC__, so the same order names it Clang there too.
Both took the samples’ flags (-std=c++23 -Wall -Wextra, -O0, -O2). The g++ 16.2 on Compiler Explorer rejected Clang’s -Wthread-safety and -stdlib=libc++ as unrecognized command-line option (GCC’s C++ dialect options document -stdlib= “when G++ is configured to support this option”). clang++ 23.1 answered GCC’s -Wduplicated-cond with unknown warning option and built, but rejected -fanalyzer as an unknown argument.

The default standard: C++20 on GCC 16, C++17 on Clang 23.1
With no -std flag:
| Compiler | __cplusplus |
Standard |
|---|---|---|
| GCC 16.2 | 202002L | C++20, GNU dialect |
| Clang 23.1, Linux | 201703L | C++17, GNU dialect |
| Clang 19.1, Visual Studio | 201402L | C++14 |
| MSVC 19.44 | 199711L; _MSVC_LANG 201402L |
C++14 |
GCC’s C++ status page says “C++20 mode is the default since GCC 16” (GCC 11 to 15 defaulted to C++17); Clang’s C++ status page says “By default, Clang 16 or later builds C++ code according to the C++17 standard”. So code that needs C++20, like this std::span parameter, passes on GCC 16 with no flag and fails on Clang 23.1:
int total_jobs(std::span<const int> jobs) { // C++20: std::span
int sum = 0;
for (int j : jobs) sum += j;
return sum;
}— end example]
GCC 16.2 built it. Clang 23.1, on its default libstdc++, said:
error: no member named 'span' in namespace 'std'
6 | int total_jobs(std::span<const int> jobs) { // C++20: std::span
| ^~~~— end example]
libstdc++ or libc++: the standard library is the bigger difference
Clang compiles against the library it is given. With no flag, Clang 23.1 on Linux (Compiler Explorer) used libstdc++, and -stdlib=libc++ switched it to LLVM’s libc++; libc++’s own page calls libc++ the default on Apple’s platforms, Android and FreeBSD, and the Visual Studio Clang 19.1 used Microsoft’s STL. In every sample below, a difference went with the library, not the compiler: when Clang rejects a library feature, check which library it was on.
Which libstdc++ is the trap. <version> defines _GLIBCXX_RELEASE and __GLIBCXX__; built with no flags on Compiler Explorer, Clang 23.1 printed:
library libstdc++
__GLIBCXX__ 20250808
_GLIBCXX_RELEASE 15— end example]
That is GCC 15’s library, though GCC 16.2 was installed beside it: Clang 23.1’s -v listed GCC 15.2 as its only candidate installation. Clang takes the libstdc++ of the GCC it is set up to find, which need not be the newest on the machine. With --gcc-toolchain= it took 16, and a program built that way failed to start on the older runtime (version `GLIBCXX_3.4.36' not found) until linked with -static-libstdc++.
What each library has and the other lacks, by their __cpp_lib_ macros under -std=c++26:
| Library | Has, and the other lacks |
|---|---|
| libstdc++ 16, C++23 | std::generator, <stacktrace>, std::, <spanstream>, views::chunk, views::slide |
| libstdc++ 16, C++26 | std::inplace_vector, std::indirect, std::polymorphic, std:: |
| libc++ 23 | constexpr std::map, std::set, std::list and std::queue; std:: |
std::generator is the one most code meets (the sample prints with std::println):
std::generator<int> job_ids(int count) { // C++23: std::generator
for (int id = 1; id <= count; ++id) co_yield id;
}— end example]
libc++ 23 stopped at the include:
fatal error: 'generator' file not found
3 | #include <generator>
| ^~~~~~~~~~~— end example]
Clang 23.1 on libstdc++ 16 built and ran it; C++ generators with co_yield and std::generator has a C++20 generator for a library without one. The other way round, a std::map in a constexpr function built on libc++ 23 only.
Layouts differ too (64-bit release builds; with /MDd, Microsoft’s std::string is 40 bytes and vector<int> 32):
| Type | Microsoft’s STL | libstdc++ 16 | libc++ 23 |
|---|---|---|---|
sizeof(std::string) |
32 | 32 | 24 |
sizeof(std:: |
64 | 32 | 48 |
std:: |
15 | 15 | 22 |
vector<int>, optional<int>, shared_ptr<int> |
24, 8, 16 | 24, 8, 16 | 24, 8, 16 |
Clang 23.1 on libstdc++ 16 printed the libstdc++ column exactly: sizes belong to the library. So do mangled names: libc++ puts its types in std::__1, except low-level ones it shares with libstdc++ (std::exception is St9exception on both), and libstdc++ puts some in std::__cxx11; typeid(std:: printed NSt7__cxx1112basic_string on libstdc++ and NSt3__112basic_string on libc++.
Clang’s Toolchain page says code built on each library can be linked into one program “so long as you do not attempt to pass C++ standard library objects across the boundary”. It adds that “it is not generally possible to have more than one C++ ABI library in a program”; no such link was run here. Each library also has its own hardening switch: the std::optional answer compares the three.
C++20 to C++26: what each compiler has
For C++23 the two are close. Of the C++23 language feature-test macros, only __cpp_deduction_guides is lower on Clang (201703L, against GCC’s 202207L), and Clang’s status page lists four C++23 papers as No and one as Partial, among them constexpr <cmath>. The rows that decide something, two of them C++20 (C++26 is technically complete since March 2026):
| Feature, macro | GCC 16.2 | Clang 23.1 |
|---|---|---|
Non-type template arguments (C++20, P1907R1), __cpp_nontype_template_args |
201911L | 201411L; P1907R1 partial since Clang 18 |
Modules (C++20), __cpp_modules |
201810 with -fmodules, not defined without |
1 |
Contracts (C++26), __cpp_contracts |
202502L; links with -lstdc++exp |
not defined; pre rejected |
Reflection (C++26), __cpp_impl_reflection |
202603L with -freflection |
not defined; no <meta> |
Expansion statements (C++26), __cpp_expansion_statements |
202506L | not defined: builds over a braced list; over an array, iterating expansion statements are not yet supported |
constexpr exceptions (C++26), __cpp_constexpr_exceptions |
202411L | not defined |
#embed (C++26), __cpp_pp_embed |
202502L | not defined; builds as a Clang extension, with a warning |
| Fold-expression constraint ordering (C++26, P2963R3), no macro | call of overloaded 'pick(char, char)' is ambiguous |
builds, since Clang 19 |
Neither modules value reaches C++20’s 201907L, so a __cpp_modules >= 201907L test fails on both.
Reflection on GCC 16.2, with -std=c++26 -freflection, printed jobs and cached:
struct Target {
int jobs;
bool cached;
};
// ...
constexpr auto ctx = std::meta::access_context::unchecked();
static constexpr auto members = std::define_static_array(
std::meta::nonstatic_data_members_of(^^Target, ctx));
template for (constexpr auto m : members) // C++26
std::println("{}", std::meta::identifier_of(m));— end example]
Without -freflection, GCC said reflection is only available with '-freflection'; the other three have no <meta>. Contracts compile on GCC 16.2 with -std=c++26 alone:
int jobs_for(int cores)
pre(cores > 0)
post(r : r >= 2)
{
return cores * 2;
}— end example]
They link only with -lstdc++exp; without it, handle_contract_violation is an undefined reference. Linked, the program printed 8 jobs, and with 0 cores it reported the contract violation on stderr and ended in SIGABRT. Clang 23.1 said expected function body after function declarator.
The other way, Clang has had P2963R3, which orders constraints that use fold expressions, since Clang 19: Clang 23.1 and 19.1 picked the overload constrained by (Tiny<Ts> && ...) over the one constrained by (Small<Ts> && ...), while GCC 16.2 called the pair ambiguous.
Clang 23.1 leaves two macros undefined for features it has only in part: template for over a braced list builds but not over an array, and #embed builds as a Clang extension with a warning (its status page lists C++ #embed for Clang 24). Clang 19.1 rejects template for. Clang 23.1 still defines __cpp_trivial_relocatability and rejects the trivially_relocatable_if_eligible keyword: trivial relocation was taken out of C++26 in November 2025 (P3920R0). First releases per feature are in Since When’s C++23 table and its C++26 table.
Diagnostics: one mistake, two messages, and warnings only one has
The same forgotten const:
struct Target {
int jobs = 4;
int job_count() { return jobs; } // const forgotten
};
int total_jobs(const Target& t) {
return t.job_count(); // error: job_count() is not const
}— end example]
GCC 16.2:
In function 'int total_jobs(const Target&)':
error: passing 'const Target' as 'this' argument discards qualifiers [-fpermissive]
11 | return t.job_count(); // error: job_count() is not const
| ~~~~~~~~~~~^~
note: in call to 'int Target::job_count()'
7 | int job_count() { return jobs; } // const forgotten
| ^~~~~~~~~— end example]
Clang 23.1, and Clang 19.1 word for word:
error: 'this' argument to member function 'job_count' has type 'const Target', but function is not marked const
11 | return t.job_count(); // error: job_count() is not const
| ^
note: 'job_count' declared here
7 | int job_count() { return jobs; } // const forgotten
| ^— end example]
For an unsatisfied constraint (std::ranges::sort on a std::list), GCC 16.2 printed a bulleted tree, starting • there are 2 candidates; Clang printed flat notes.
Who warned, at -O0 and -O2 (“never” means not even with Clang’s -Weverything; Clang 19.1 and 23.1 agreed):
| Mistake | GCC 16.2 | Clang 23.1 |
|---|---|---|
string_view of a temporary std::string |
none at -Wall -Wextra |
-Wdangling-gsl, at -Wall -Wextra |
const int& to std::max of a temporary |
-Wdangling-reference, at -Wall -Wextra |
-Wdangling, at -Wall -Wextra; none on libstdc++ 16 |
jobs set on one path only |
-Wmaybe-uninitialized, at -Wall -Wextra with -O2 |
-Wsometimes-uninitialized, at -Wall -Wextra |
an else if that repeats the if |
only with -Wduplicated-cond |
never |
flags && 0x4 |
only with -Wlogical-op |
-Wconstant-logical-operand, at -Wall -Wextra |
jobs < 1 && jobs > 64 |
only with -Wlogical-op |
-Wtautological-overlap-compare, at -Wall -Wextra |
static_cast<int> of an int |
only with -Wuseless-cast |
never |
| a guarded member written without its lock | no such warning | only with -Wthread-safety |
jobs[i] through a raw pointer |
no such warning | only with -Wunsafe-buffer-usage |
GCC found the possibly uninitialized jobs only at -O2; its manual says that warning is “only possible in optimizing compilation”. Clang’s dangling const int& warning came with libc++ and Microsoft’s STL, not with libstdc++ 16. The std::span answer has the same table for a span that dangles.
Clang’s thread-safety attributes “can be safely erased when compiling with other compilers”, and the page’s own mutex.h erases them: unguarded, each cost a GCC 16.2 warning ('capability' attribute directive ignored).
#if defined(__clang__)
#define TS(x) __attribute__((x))
#else
#define TS(x) // the other compilers: no attributes at all
#endif
// ...
struct JobQueue {
Lock lock;
int pending TS(guarded_by(lock)) = 0;
void add() { ++pending; } // the lock is not held
};— end example]
With -Wthread-safety, both Clangs said:
warning: writing variable 'pending' requires holding mutex 'lock' exclusively [-Wthread-safety-analysis]— end example]
Sanitizers, tools and licenses
The table’s sanitizer and tool rows come from each project’s documentation; none was run here. clang-tidy is “a clang-based C++ ‘linter’ tool”, so it reads your code as Clang does: one more reason to keep a GCC project building on Clang. Toolchain lists the compilers, debuggers and analyzers with their platforms and prices.
GCC is GPL version 3 or later. Its runtime libraries carry the GCC Runtime Library Exception, which grants “permission to propagate a work of Target Code formed by combining the Runtime Library with Independent Modules” when “all Target Code was generated by Eligible Compilation Processes”. Such a combination may then be conveyed “under terms of your choice, consistent with the licensing of the Independent Modules”. LLVM is under the “Apache License, Version 2.0, with two limited exceptions”, and the LLVM Developer Policy says LLVM’s license “is not a ‘copyleft’ license like the GPL”.
Speed of the compiler and of the code it builds
Not measured: none of the samples was timed. Clang’s pages state a goal (“fast, light and scalable”), no GCC page consulted makes a speed claim, and the published benchmarks compare older releases (GCC 13 against Clang 16) on one machine. Build your own code with both at the same -O level and measure.
Which to choose, and how to build with both
| Situation | Use | Because |
|---|---|---|
| Linux | either; Clang on the system’s libstdc++ | one ABI and, by default, one library |
| macOS, iOS | Apple’s Clang | Apple’s own build; libc++ is the default there |
| Windows, Microsoft’s ABI | MSVC, clang-cl or the Visual Studio Clang | Microsoft’s STL; clang-cl is “designed to be compatible with” cl.exe |
| Windows, GNU tools | GCC or Clang with MinGW-w64 | MinGW-w64 supplies the Windows headers and libraries |
| C++26 reflection or contracts now | GCC 16 | Clang 23.1 has neither |
| Duplicated conditions and useless casts at compile time | GCC | -Wduplicated-cond, -Wuseless-cast |
| Lock and buffer checks at compile time | Clang | -Wthread-safety, -Wunsafe-buffer-usage |
| Uninitialized reads at run time | Clang | MemorySanitizer: Linux, NetBSD, FreeBSD; every library instrumented, no static linking |
| Any project that can | both, in CI | each warns where the other is silent |
The same project with each compiler, and Clang on either library:
CXX=g++ cmake -S . -B build-gcc
CXX=clang++ cmake -S . -B build-clang
# or name the compiler explicitly; this one builds on libc++
cmake -S . -B build-libcxx -DCMAKE_CXX_COMPILER=clang++ \
-DCMAKE_CXX_FLAGS=-stdlib=libc++
# Clang on libc++ (g++ takes -stdlib= only if built for it)
clang++ -std=c++23 -stdlib=libc++ -Wall -Wextra -c main.cpp
# Clang on the libstdc++ of one chosen GCC installation
clang++ -std=c++23 --gcc-toolchain=/opt/gcc-16.2.0 -c main.cpp— end example]
- Pass
-std=(/std:on MSVC) to every compiler: with none, GCC 16, Clang 23.1 on Linux and MSVC pick C++20, C++17 and C++14. - Turn on the same checks on both:
-Wall -Wextra, plus each compiler’s own flags in its own branch of the build, since g++ 16.2 rejected Clang’s-Wthread-safety. - Build and run the tests with both in CI.
- One standard library per program: no standard type passes between code built on libstdc++ and code built on libc++.
More on compilers, flags and build setups is in Tooling.
Questions
What is the difference between LLVM and Clang?
LLVM is the project, in its own words “a collection of modular and reusable compiler and toolchain technologies”, with the optimizer and code generators at its core. Clang is its “LLVM native” C, C++ and Objective-C compiler, the front end. libc++, the lld linker and the lldb debugger are LLVM projects too, so “LLVM” names the whole family and “Clang” the compiler.
LLVM vs GCC: how do the two toolchains compare?
Each is a whole toolchain. GCC brings the compiler, its libstdc++ and gcov, and is used with GDB, a separate GNU project. LLVM brings Clang, libc++, the lld linker, the lldb debugger, and clang-tidy, clang-format and clangd. GCC’s runtime libraries are GPLv3 with the GCC Runtime Library Exception; LLVM is Apache 2.0 with two LLVM exceptions.
What is the difference between gcc, g++ and cc?
All three start a compiler. g++ compiles .c, .h and .i files as C++ unless -x says otherwise, and links the C++ standard library; gcc compiles .cpp files as C++ too but does not add that library, so link C++ programs with g++. cc and c++ are the names a system gives its default C and C++ compilers: cc --version shows which one yours is.
Can I use libc++ with GCC, and which standard library does Clang use?
Only with a g++ built for it: GCC documents -stdlib=libstdc++ and -stdlib=libc++ “when G++ is configured to support this option”; the g++ 16.2 on Compiler Explorer was not, and rejected -stdlib=libc++ with “unrecognized command-line option”. libc++’s own page lists GCC 16 among the compilers it is tested with. Clang 23.1 on Linux (Compiler Explorer) used libstdc++ with no flag and libc++ with -stdlib=libc++; libc++ is the default on Apple’s platforms, Android and FreeBSD, and the Visual Studio Clang uses Microsoft’s STL.
Can I link object files built by GCC and by Clang?
On Linux the ABI is built for it: both compilers implement the Itanium C++ ABI, whose aim is to let users “build relocatable objects with different compilers and link them together” (no link was run here). Keep one standard library and one release of it: GCC 16 changed std::variant’s C++17 ABI and some C++20 types. Clang’s Toolchain page allows libstdc++ code and libc++ code in one program only if no standard-library object crosses between them and there is one C++ ABI library.
Is Clang a drop-in replacement for GCC?
For the command line and most GNU extensions, nearly: Clang 23.1 took the GCC flags the samples used, defined __GNUC__ as 4 on Linux, and answered an unknown GCC warning flag with a warning, not an error. Its manual lists GCC extensions it leaves out, such as __builtin_va_arg_pack and decimal floating-point types, and a GCC-only option such as -fanalyzer is an error. The default standard differs too (C++17 on Clang 23.1 for Linux, C++20 on GCC 16), so pass -std= to both.
Sources
- C++ Standards Support in GCCGCC
- GCC 16 Release Series: Changes, New Features, and FixesGCC
- Clang: C++ Programming Language StatusLLVM Project
- Assembling a Complete ToolchainClang documentation
- Clang Compiler User’s ManualClang documentation
- libc++ C++ Standard LibraryLLVM Project
- Itanium C++ ABIItanium C++ ABI
- GCC Runtime Library ExceptionFree Software Foundation