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
  1. 1Clang vs GCC at a glance
  2. 2What is the same: language, ABI and most flags
  3. 3The default standard: C++20 on GCC 16, C++17 on Clang 23.1
  4. 4libstdc++ or libc++: the standard library is the bigger difference
  5. 5C++20 to C++26: what each compiler has
  6. 6Diagnostics: one mistake, two messages, and warnings only one has
  7. 7Sanitizers, tools and licenses
  8. 8Speed of the compiler and of the code it builds
  9. 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.

A sheet of code on the left; two parallel tracks of three equal boxes leave it, one black and one magenta, and both end in arrows on one hatched block on the right.
Figure 1 — GCC and Clang: two compilers, one language and, on Linux, one ABI.

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::is_within_lifetime
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:

[Example 1:
// 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:

[Example 2:
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.

Two stacks. g++: GCC front end, optimizer, code generator, libstdc++. clang++: Clang front end, LLVM optimizer, code generator, libstdc++ or libc++. Under both: the Itanium C++ ABI.
Figure 2 — On Linux, GCC and Clang produce objects for the same Itanium C++ ABI, and Clang can use either standard library: libstdc++ by default, or libc++ with -stdlib=libc++.

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:

[Example 3:
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:

[Example 4:
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:

[Example 5:
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::move_only_function, <spanstream>, views::chunk, views::slide
libstdc++ 16, C++26 std::inplace_vector, std::indirect, std::polymorphic, std::copyable_function
libc++ 23 constexpr std::map, std::set, std::list and std::queue; std::is_within_lifetime

std::generator is the one most code meets (the sample prints with std::println):

[Example 6:
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:

[Example 7:
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::function<void()>) 64 32 48
std::string{}.capacity() 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::string).name() printed NSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE on libstdc++ and NSt3__112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEE 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:

[Example 8:
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:

[Example 9:
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:

[Example 10:
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:

[Example 11:
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:

[Example 12:
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).

[Example 13:
#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:

[Example 14:
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:

[Example 15:
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.

Read next

  1. C++ vs C: what differs, what breaks, which to chooseCareers · Tobias Mwangi · written for C++23
  2. std::expected in C++23: return a value or an errorStandard Library · Miriam Castellanos · written for C++23
  3. C++ generic lambdas, template lambdas and function objectsTemplates · Miriam Castellanos · written for C++20