Understanding the different flavors of Clang C and C++ compilers in Windows

https://blog.conan.io/2022/10/13/Different-flavors-Clang-compiler-Windows.html

This article will explain the different flavors of Clang C and C++ compiler you might encounter in Windows, and give you some suggestions about which ones might be right for you, together with detailed instructions on how to use them with CMake and Conan.

Introduction

The Microsoft C and C++ compiler (msvc or cl.exe) has been predominant for the last decades on Windows,
and while the MinGW tools have been providing a working GNU ecosystem (with gcc/g++ compilers) for Windows for many years,
it never gained widespread traction.

The Clang compiler, built on the shoulders of the LLVM giant has been gaining traction in the last years,
first powering the C and C++ Apple development (now they have their own apple-clang fork),
then gaining the attention of many developers for its excellent developer tools, parser infrastructure, sanitizers, formatters,
and code quality and security frameworks.
This trend has finally started catching on in Windows too,
with more and more developers wanting to have a common compiler infrastructure in all OS,
being even bundled and distributed in Microsoft Visual Studio IDE.

However, there are a few different variants of the Clang compiler in Windows,
and it is not always evident what their differences are or when/how to use them so let’s take a deeper look at them.

The different flavors

There are several different ways to install the Clang compiler on Windows, let’s enumerate and name them:

We can group them in 2 big families:

  • Windows subsystems based: They are all different compilers using different runtime libraries with potentially different compilation/linking flags.
    They do not intend to be compatible with msvc, nor even between the different subsystems.

  • Visual Studio based: The LLVM/Clang and the Visual Studio ClangCL, as we will check later, are actually the same, just bundled and distributed in a different way.
    They use and link the Visual Studio runtime, and are intended to be compatible with the Visual studio msvc compiler

Different runtimes

One of the major and important differences between these compilers is what C++ standard library implementation and what runtime libraries they are going to use.
Checking the runtime dependencies can be done with the following setup (doing this with Conan is detailed later):

A static library that implements one single method hello() that outputs several preprocessor definitions:

#include <iostream>
#include "hello.h"
 
void hello(){
    // ARCHITECTURES
    #ifdef _M_X64
    std::cout << "  hello/0.1: _M_X64 defined\n";
    #endif
 
    // COMPILER VERSIONS
    #if _MSC_VER
    std::cout << "  hello/0.1: _MSC_VER" << _MSC_VER<< "\n";
    #endif
 
    #if _MSVC_LANG
    std::cout << "  hello/0.1: _MSVC_LANG" << _MSVC_LANG<< "\n";
    #endif
 
     // MORE FLAGS
}

An executable that uses the library:

#include "hello.h"
 
int main() {
    hello();
}

A CMakeLists.txt to build it:

cmake_minimum_required(VERSION 3.15)
project(hello CXX)
 
add_library(hello src/hello.cpp)
target_include_directories(hello PUBLIC include)
 
add_executable(example src/example.cpp)
target_link_libraries(example hello)

When the example.exe executable is built, it is possible to use the
dumpbin command to list its required shared libraries:

$ dumpbin /nologo /dependents "./path/to/example.exe"

This dumpbin command is not in the PATH by default,
so it needs to be run in a Visual Studio prompt, or activate such prompt with the vcvars batch file (or any alternative solution, see dumpbin reference for details):

$ set "VSCMD_START_DIR=%CD%" && call "C:/Program Files/Microsoft Visual Studio/2022/Community/VC/Auxiliary/Build/vcvarsall.bat" amd64 && dumpbin /nologo /dependents "./path/to/example.exe"

This is the summary of the runtime dependencies of such executable for the different flavors (including the msvc compiler as a reference):

Note: the KERNEL32.dll is always a system runtime dependency, in all cases, it has been omitted in the table.

Let’s have a look and explain these results.
The first relevant item is that all msvc,
LLVM/Clang and Visual Studio ClangCL are using the same runtimes.
This is because the LLVM/Clang compiler uses the MSVC APIs and libraries implementations.
While this is more evident or expected from Visual Studio ClangCL, it is a bit more surprising for the LLVM/Clang release.
It happens that such a release implements the location of the MSVC libraries in the system.
Latest LLVM/Clang releases default to using Visual Studio 17 2022,
but it is possible to force using other installations by defining the appropriate Visual Studio environment.

The MSVC libraries have different Release and Debug versions.
The Debug versions append a D, like in MSVCP140D.dll,
and the Debug builds can introduce runtime dependencies to other libraries like ucrtbased.dll,
that aren’t necessarily used in the Release builds.

Also, the three msvc, LLVM/Clang and Visual Studio ClangCL compilers use the api-ms-win-crt-*.dll libraries at runtime.
These libraries can be found installed in the Windows systems folders,
but LLVM/Clang also redistributes a copy that can be found in its “bin” folder,
together with the clang.exe and clang++.exe executable files.

The Msys2 MinGW Clang links with the GNU libstdc++ (it can link with libstdc++ or libstdc++11),
resulting in a runtime dependency to libstdc++6.dll and msvcrt.dll,
instead of the libc++.dll that Msys2 Clang uses.
It might be worth noting that the msvcrt.dll is considered to be an older Windows API compared with api-ms-win-crt-*.dll,
but also more backwards compatible with older Windows OS versions (7, 8), while the latter only comes by default in Windows 10.

It is noticeable that Cygwin Clang is no longer being maintained, the latest version available there is clang 8.0.1. It is still added here for reference, and for those still having to deal with such legacy systems. The executables built with this Cygwin Clang compiler, use the cygstdc++-6.dll and cygwin1.dll runtimes when being instructed to use the libstdc++ (or libstdc++11). But they can also be told to use libc++, and that results in using a different runtime, depending on cygc++-1.dll, cygc++abi-1.dll, cyggcc_s-seh-1.dll, cygwin1.dll instead.

It is possible for all flavors to statically link the C++ standard libraries statically inside executables,
in that case the C++ runtime libraries are no longer an explicit dependency,
and only the system runtime will be reported by dumpbin.
This can also happen if we build a pure C application.
However, this doesn’t mean that we can mix different libraries that depend on different runtimes in our applications,
as they will be binary incompatible.

Compiler differences

The runtime libraries used are not the only difference between the different Clang compilers,
but there are also ABI differences between the flavors, like the size of some types as long double.
It is also very important to note that each flavor will use different preprocessor directives,
which can have a very heavy impact on the created binaries,
specially for multi-platform, multi-compiler optimized code that will rely on such preprocessor definitions.

Here is a table summarizing some different preprocessor definitions (details on how to reproduce this table are explained below):

On the architecture side, all Clang versions, except the obsolete Cygwin one, will declare both the MSVC specific _M_X64 and the GNU and Clang __x86_64__ one.

The compiler version varies between the 2 major families.

  • The VS based ones will define _MSC_VER=1933, (that belongs to v143 or 19.3 compiler version, the default one in Visual Studio 17 2022),
  • together with the __clang_major__=13 one for the Clang compiler.

Note how the Visual Studio ClangCL and the LLVM/Clang versions are different,
in this case, we are using the LLVM/Clang 13.0 version so we can manually check our build used the correct compiler (the Visual Studio ClangCL bundled one is version 14)

It is completely possible to use a different VS version when using LLVM/Clang.
By default it will use the latest v143 runtime,
but by activating the environment (via vcvars or running in another VS version prompt) it is possible to use that version instead.
We will see it later in the tests, but in essence it is evidenced by a preprocessor definition like _MSC_VER=192X.
It is also possible to alter the default MSVC compatibility behavior with the compiler option -fms-compatibility-version, which makes clang behave like other MSVC version, or -fmsc-version to just override the _MSC_VER value.

On the other hand, the Msys2 based Clang compilers will define __GNUC__ flags instead, while still using the __clang_major__=13 definitions.
These definitions can be controlled with the -fgnuc-version compiler option,
but note that it doesn’t really activate or deactivate the GNU extensions in Clang, just change the values of the preprocessor definitions.

Something similar happens with the C++ standard.
The VS based Clang compilers will define both the VS specific _MSVC_LANG=201402 (C++14 standard),
and the C++ standard __cplusplus=201402.
Note however, how the Msys2 MinGW Clang might also define the _GLIBCXX_USE_CXX11_ABI to instruct to use the libstdc++ or the libstdc++11 C++ standard library.

Note how the vanilla msvc will report __cplusplus199711, even if the _MSVC_LANG=201402 is correct to represent C++14.
You would need to explicitly define /Zc:__cplusplus to have __cplusplus correctly defined.

...
...

Msys2 Clang

The first step to use this compiler is to install Msys2, it can be done following the instructions at the Msys2 site.
To install and uninstall things inside the different Msys2 environments, the pacman system package manager can be used.
To install the development Clang toolchain, inside the clang64 environment, it is possible to install it with

pacman -S mingw-w64-clang-x86_64-toolchain

This will be the profile to use for this case:

[settings]
os=Windows
arch=x86_64
build_type=Release
compiler=clang
compiler.version=14
compiler.cppstd=gnu14
compiler.libcxx=libc++
 
[buildenv]
PATH+=(path)C:/ws/msys64/clang64/bin
 
[runenv]
PATH+=(path)C:/ws/msys64/clang64/bin
 
[conf]
tools.env.virtualenv:auto_use=True

There are some important differences with respect to the previous MSVC-based Clang compilers:
The compiler.runtime settings are no longer defined.
Such runtime settings refer to Windows and MSVC runtime.
And it not relevant to the GNU toolchain In Clang and GNU compilers,
the stdlib is managed by the compiler.libcxx which in this case is set to libc++
Besides the [buildenv] PATH pointing to the location of the Clang compiler inside Msys2,
it is necessary the equivalent in runtime [runenv], so when the conanfile.py executes the executable,
it can find the libc++.dll and libunwind.dll dynamic libraries.
The above compilers runtimes were in the system and automatically picked up.

https://packages.msys2.org/groups/mingw-w64-clang-x86_64-toolchain

Group: mingw-w64-clang-x86_64-toolchain
21 packages
Installation:
pacman -S mingw-w64-clang-x86_64-toolchain
Packages:
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-clang-analyzer
mingw-w64-clang-x86_64-clang-tools-extra
mingw-w64-clang-x86_64-compiler-rt
mingw-w64-clang-x86_64-gcc-compat
mingw-w64-clang-x86_64-lld
mingw-w64-clang-x86_64-llvm
mingw-w64-clang-x86_64-crt-git
mingw-w64-clang-x86_64-headers-git
mingw-w64-clang-x86_64-libc++
mingw-w64-clang-x86_64-libunwind
mingw-w64-clang-x86_64-libmangle-git
mingw-w64-clang-x86_64-libwinpthread-git
mingw-w64-clang-x86_64-winpthreads-git
mingw-w64-clang-x86_64-lldb
mingw-w64-clang-x86_64-make
mingw-w64-clang-x86_64-openmp
mingw-w64-clang-x86_64-pkgconf
mingw-w64-clang-x86_64-polly
mingw-w64-clang-x86_64-tools-git
mingw-w64-clang-x86_64-winstorecompat-git

Msys2 MinGW Clang

This compiler is installed with
pacman -S mingw-w64-x86_64-clang inside the Msys2 MinGW64 terminal.
Note the command is not exactly the same as the above (that uses the mingw-w64-clang-x86_64-toolchain package instead).

The profile used for this configuration is:

msys2_mingw_clang.profile

[settings]
os=Windows
arch=x86_64
build_type=Release
compiler=clang
compiler.version=14
compiler.cppstd=gnu14
compiler.libcxx=libstdc++11
 
[buildenv]
PATH+=(path)C:/ws/msys64/mingw64/bin
 
[runenv]
PATH+=(path)C:/ws/msys64/mingw64/bin
 
[conf]
tools.env.virtualenv:auto_use=True

The main differences with the above Msys2 Clang are:

  • PATH+=(path)C:/ws/msys64/mingw64/bin points to a different location, the one of MinGW64, not the one of clang

  • compiler.libcxx=libstdc++11 now uses the gcc libstdc++ or libstdc++1 C++ standard library instead of the libc++ used by Msys2 Clang

...

...

Conclusions

The Cygwin Clang has been overwhelmingly replaced by the Msys2 flavors for this reason we are suggesting starting new efforts using the other flavors.

To decide between the MSVC based Clang flavors (LLVM/Clang and Visual Studio ClangCL) and the Msys2 based ones,
the decision should be made depending on the other project constraints.
In general the MSVC based Clang provides better compatibility with other Windows binary libraries and the system in general,
while using the Msys2 subsystem flavor can work better if there are some other dependencies that heavily rely on GNU tools and wouldn’t work well with the MSVC compiler.

Between Msys2 Clang and Msys2 MinGW Clang, the decision should be made similarly.
If there are other dependencies of the project that rely on libstdc++ runtime that have to play together, Msys2 MinGW Clang is probably the way to go,
but otherwise, it seems that Msys2 Clang with libc++ could be the way to go.

In MSVC-based Clang, between LLVM/Clang and Visual Studio ClangCL, the final decision is probably the IDE.
If a lot of development happens on Windows and developers want to use Visual Studio IDE, then the Visual Studio ClangCL will be better.
If on the contrary most development happens in Linux, and Windows devs are happy with VS Code editor and using Ninja (which many developers love because of it being very fast), then LLVM/Clang could be a better choice.

https://www.msys2.org/docs/environments/

MSYS2

MSYS2 comes with different environments/subsystems and the first thing you have to decide is which one to use.
The differences among the environments are mainly

  • environment variables,
  • default compilers/linkers,
  • architecture,
  • system libraries used etc.

If you are unsure, go with UCRT64.
The MSYS environment contains the unix-like/cygwin based tools, lives under /usr and is special in that it is always active.
All the other environments inherit from the MSYS environment and add various things on top of it.

For example, in the UCRT64 environment the $PATH variable starts with
/ucrt64/bin:/usr/bin so you get all

  • ucrt64 based tools as well as
  • all msys tools.

The active environment is selected via the MSYSTEM environment variable.
Setting MSYSTEM to UCRT64 and starting a login shell will put you in that environment.

GCC vs LLVM/Clang
These are the default compilers/toolchains used for building all packages in the respective repositories.

GCC based environments:

  • Widely tested/used at this point
  • Fortran support
  • While there also exists a Clang package in the MINGW environments, that one still uses the GNU linker and the GNU C++ library. In some cases Clang is used to build packages as well there, in case upstream prefers Clang over GCC for example.

LLVM/Clang based environments:

  • Only uses LLVM tools, LLD as a linker, LIBC++ as a C++ standard library
  • Clang provides ASAN support
  • Native support for TLS (Thread-local storage)
  • LLD is faster than LD, but does not support all the features LD supports
  • Some tools lack feature parity with equivalent GNU tools
  • Supports ARM64/AArch64 architecture on Microsoft Windows 10

MSVCRT vs UCRT

These are two variants of the C standard library on Microsoft Windows.

MSVCRT (Microsoft Visual C++ Runtime) is available by default on all Microsoft Windows versions,
but due to backwards compatibility issues is stuck in the past, not C99 compatible and is missing some features.

  • It isn't C99 compatible, for example the printf() function family, but...
  • mingw-w64 provides replacement functions to make things C99 compatible in many cases
  • It doesn't support the UTF-8 locale
  • Binaries linked with MSVCRT should not be mixed with UCRT ones because the internal structures and data types are different. (More strictly, object files or static libraries built for different targets shouldn't be mixed. DLLs built for different CRTs can be mixed as long as they don't share CRT objects, e.g. FILE*, across DLL boundaries.) Same rule is applied for MSVC compiled binaries because MSVC uses UCRT by default (if not changed).
  • Works out of the box on every version of Microsoft Windows.

UCRT (Universal C Runtime) is a newer version which is also used by Microsoft Visual Studio by default.
It should work and behave as if the code was compiled with MSVC.

  • Better compatibility with MSVC, both at build time and at run time.
  • It only ships by default on Windows 10 and for older versions you have to provide it yourself or depend on the user having it installed.
posted @ 2023-04-13 12:11  fndefbwefsowpvqfx  阅读(326)  评论(0编辑  收藏  举报