C++ Module 15 – Build systems, testing and tooling
Portability across compilers, platforms and judges
Port C++ between GCC, Clang and MSVC on Linux, macOS, Windows and online judges: type sizes, extensions and missing headers, each one recorded.
What you will learn
- Name the four places where portable C++ most often breaks, and spot each in a program
- Check the size of long, the sign of char and the width of long double for a target at compile time
- Match a contest template to the compiler and library an online judge publishes
- Rewrite a GCC-only program in standard C++ that GCC, Clang and MSVC build without warnings
- Plan a CI matrix that catches the differences that compile everywhere
Before you start
What you’ll learn · Before you start
What you will learn
- Name the four places where portable C++ most often breaks, and spot each in a program
- Check the size of long, the sign of char and the width of long double for a target at compile time
- Match a contest template to the compiler and library an online judge publishes
- Rewrite a GCC-only program in standard C++ that GCC, Clang and MSVC build without warnings
- Plan a CI matrix that catches the differences that compile everywhere
Before you start
On this page
Ten numbers below a billion add up to 4,218,110,479 with GCC on a Mac and to −76,856,817 with MinGW GCC on Windows, from the same file. Portable C++ builds and behaves the same with GCC, Clang and MSVC, on Linux, macOS and Windows, and on the online judges that grade contest solutions.
Portability across compilers breaks in four predictable places: headers that belong to one library, such as
<bits/stdc++.h>; compiler extension: A feature a compiler accepts beyond the C++ standard, such as GCC's __int128 or variable-length arrays. Code that relies on one builds only with the compilers that offer it, so portable code keeps it behind a feature test., such as __int128 and variable-length
arrays; types whose size or sign depends on the platform, such as long, which is 32 bits wide in Windows programs
but 64 bits wide in 64-bit Linux and macOS programs; and library features that arrive in different releases. The
cure is standard C++ with fixed-width types, extensions behind feature tests, and builds on at least two compilers and
two platforms in continuous integration. Portability across compilers is only half the job, because one compiler
behaves differently on different platforms.
One program gets four answers from four compilers
This program is written the way many contest solutions are:
// Contest habits that only GCC accepts as they are: one header for everything, 128-bit integers, a variable-length
// array, and long used as a 64-bit type.
#include <bits/stdc++.h>
using namespace std;
long mulmod(long a, long b, long m) { // (a * b) % m without overflow, through 128 bits
return (long)((__int128)a * b % m);
}
int main(int argc, char**) {
const int n = argc + 9; // 10 when the program runs without arguments
long powers[n]; // a variable-length array
powers[0] = 1;
for (int i = 1; i < n; ++i) powers[i] = mulmod(powers[i - 1], 1'000'000'007L, 998'244'353L);
long total = 0;
for (int i = 0; i < n; ++i) total += powers[i];
printf("long has %zu bytes; total %ld\n", sizeof(long), total);
} On this Mac, Apple clang rejects it at once and GCC builds it with two warnings:
from session import run
# The GCC-only program with both compilers of this Mac: Apple clang with its library, libc++, and GCC 16 with
# libstdc++, warnings switched on.
run("""
clang++ -std=c++23 gcc_only.cpp -o gcc_only
g++-16 -std=c++23 -Wall -Wextra -pedantic gcc_only.cpp -o gcc_only && ./gcc_only
""") Output
$ clang++ -std=c++23 gcc_only.cpp -o gcc_only
gcc_only.cpp:3:10: fatal error: 'bits/stdc++.h' file not found
3 | #include <bits/stdc++.h>
| ^~~~~~~~~~~~~~~
1 error generated.
[exit status 1]
$ g++-16 -std=c++23 -Wall -Wextra -pedantic gcc_only.cpp -o gcc_only && ./gcc_only
gcc_only.cpp: In function 'long int mulmod(long int, long int, long int)':
gcc_only.cpp:7:20: warning: ISO C++ does not support '__int128' for 'type name' [-Wpedantic]
7 | return (long)((__int128)a * b % m);
| ^~~~~~~~
gcc_only.cpp: In function 'int main(int, char**)':
gcc_only.cpp:12:10: warning: ISO C++ forbids variable length array 'powers' [-Wvla]
12 | long powers[n]; // a variable-length array
| ^~~~~~
long has 8 bytes; total 4218110479
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 gcc_only_session.py
On Windows, MSVC stops at the #include too, and MinGW GCC builds it and runs it:
$ cl /std:c++latest /W4 /EHsc gcc_only.cpp
gcc_only.cpp
gcc_only.cpp(3): fatal error C1083: Cannot open include file: 'bits/stdc++.h': No such file or directory
[exit status 2]
$ cl /std:c++latest /W4 /EHsc step1.cpp
step1.cpp
step1.cpp(7): error C4235: nonstandard extension used: '__int128' keyword not supported on this architecture
step1.cpp(7): error C2059: syntax error: ')'
step1.cpp(12): error C2131: expression did not evaluate to a constant
step1.cpp(12): note: failure was caused by a read of a variable not usable in constant expressions
step1.cpp(12): note: see usage of 'n'
[exit status 2]
$ g++ -std=c++23 -Wall -Wextra -pedantic gcc_only.cpp -o gcc_only.exe
gcc_only.cpp: In function 'long int mulmod(long int, long int, long int)':
gcc_only.cpp:7:20: warning: ISO C++ does not support '__int128' for 'type name' [-Wpedantic]
7 | return (long)((__int128)a * b % m);
| ^~~~~~~~
gcc_only.cpp: In function 'int main(int, char**)':
gcc_only.cpp:12:10: warning: ISO C++ forbids variable length array 'powers' [-Wvla]
12 | long powers[n]; // a variable-length array
| ^~~~~~
$ gcc_only.exe
long has 4 bytes; total -76856817 | Compiler | Result |
|---|---|
| Apple clang 21, with libc++ | Fatal error: bits/stdc++.h not found |
| GCC 16 on macOS, with libstdc++ | Builds, two warnings; long has 8 bytes, total 4218110479 |
| MSVC 19.51 | Fatal error C1083; without that header, errors for __int128 and the array |
| MinGW GCC 16.2.0 | Builds, two warnings; long has 4 bytes, total -76856817 |
The MinGW result is the dangerous one: no error, and a wrong number. Ten values below a billion add up to more than
4 billion, which fits in an 8-byte long but overflows a 4-byte one. <bits/stdc++.h> is a file of libstdc++, GCC’s
standard library, which its manual describes as a header for precompiling; it belongs to the library, not the
compiler, so Clang finds it when it uses libstdc++, as on most Linux systems, and not when it uses libc++.
Checkpoint
Check: One program, four compilers
long, plain char and long double change with the target
Three facts about types change with the target. A header-free file checks them while compiling, and one Clang can compile it for five targets without running anything on them:
// Three facts about the target that portable code must not assume, checked while compiling: no headers needed.
static_assert(sizeof(long) == 8, "long is not 64 bits here");
static_assert(char(-1) < 0, "plain char is unsigned here");
static_assert(sizeof(long double) > sizeof(double), "long double is no wider than double here"); from session import run
# One file of compile-time checks, compiled for five targets by the same Clang. -fsyntax-only stops after the
# checks, so no library, linker or other computer is needed.
run("""
clang++ --target=arm64-apple-macos -std=c++23 -fsyntax-only models.cpp
clang++ --target=x86_64-linux-gnu -std=c++23 -fsyntax-only models.cpp
clang++ --target=aarch64-linux-gnu -std=c++23 -fsyntax-only models.cpp
clang++ --target=x86_64-pc-windows-msvc -std=c++23 -fsyntax-only models.cpp
clang++ --target=x86_64-w64-mingw32 -std=c++23 -fsyntax-only models.cpp
""") Output
$ clang++ --target=arm64-apple-macos -std=c++23 -fsyntax-only models.cpp
models.cpp:4:15: error: static assertion failed due to requirement 'sizeof(long double) > sizeof(double)': long double is no wider than double here
4 | static_assert(sizeof(long double) > sizeof(double), "long double is no wider than double here");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
models.cpp:4:35: note: expression evaluates to '8 > 8'
4 | static_assert(sizeof(long double) > sizeof(double), "long double is no wider than double here");
| ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
1 error generated.
[exit status 1]
$ clang++ --target=x86_64-linux-gnu -std=c++23 -fsyntax-only models.cpp
$ clang++ --target=aarch64-linux-gnu -std=c++23 -fsyntax-only models.cpp
models.cpp:3:15: error: static assertion failed due to requirement 'char(-1) < 0': plain char is unsigned here
3 | static_assert(char(-1) < 0, "plain char is unsigned here");
| ^~~~~~~~~~~~
models.cpp:3:24: note: expression evaluates to ''<FF>' (0xFF, 255) < 0'
3 | static_assert(char(-1) < 0, "plain char is unsigned here");
| ~~~~~~~~~^~~
1 error generated.
[exit status 1]
$ clang++ --target=x86_64-pc-windows-msvc -std=c++23 -fsyntax-only models.cpp
models.cpp:2:15: error: static assertion failed due to requirement 'sizeof(long) == 8': long is not 64 bits here
2 | static_assert(sizeof(long) == 8, "long is not 64 bits here");
| ^~~~~~~~~~~~~~~~~
models.cpp:2:28: note: expression evaluates to '4 == 8'
2 | static_assert(sizeof(long) == 8, "long is not 64 bits here");
| ~~~~~~~~~~~~~^~~~
models.cpp:4:15: error: static assertion failed due to requirement 'sizeof(long double) > sizeof(double)': long double is no wider than double here
4 | static_assert(sizeof(long double) > sizeof(double), "long double is no wider than double here");
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
models.cpp:4:35: note: expression evaluates to '8 > 8'
4 | static_assert(sizeof(long double) > sizeof(double), "long double is no wider than double here");
| ~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~
2 errors generated.
[exit status 1]
$ clang++ --target=x86_64-w64-mingw32 -std=c++23 -fsyntax-only models.cpp
models.cpp:2:15: error: static assertion failed due to requirement 'sizeof(long) == 8': long is not 64 bits here
2 | static_assert(sizeof(long) == 8, "long is not 64 bits here");
| ^~~~~~~~~~~~~~~~~
models.cpp:2:28: note: expression evaluates to '4 == 8'
2 | static_assert(sizeof(long) == 8, "long is not 64 bits here");
| ~~~~~~~~~~~~~^~~~
1 error generated.
[exit status 1]
Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 models_session.py
| Target | long |
Plain char |
long double |
|---|---|---|---|
| macOS on Apple silicon | 8 bytes | Signed | Same as double |
| Linux on x86-64 | 8 bytes | Signed | Wider than double |
| Linux on Arm64 | 8 bytes | Unsigned | Wider than double |
| Windows with MSVC | 4 bytes | Signed | Same as double |
| Windows with MinGW | 4 bytes | Signed | Wider than double |
Each row is a published convention: Microsoft documents long as 4 bytes, Arm’s procedure call standard makes
plain char: The type char written without signed or unsigned: a type of its own whose sign each platform chooses. It is signed on x86 and on Apple's arm64 platforms, and unsigned on Arm64 Linux, so bytes above 0x7F compare differently. unsigned, and Apple’s arm64 conventions keep char signed and make
long double the same as double. Compilers on one platform follow the same rules for long and char, so the
platform decides those. long double is the exception: on Windows, MinGW GCC keeps the wider format while MSVC makes
it the same as double.
Checkpoint
Check: Types that change with the target
Online judges publish the compiler you must match
Judges publish the compilers they run, and the details matter for the same reasons as on your own machines:
| Judge entry | Compiler and library | <bits/stdc++.h> |
|---|---|---|
| LeetCode | Clang 19 with libstdc++ from GCC 14, at -O2 with AddressSanitizer |
Found, and most standard headers are included for you, so the habit goes unnoticed |
| AtCoder, GCC | C++23 with GCC 15.2.0 and libstdc++ | Found |
| AtCoder, Clang | C++23 with Clang 21.1.0, built with -stdlib=libc++ |
Not found: libc++ has no such header |
Before a contest, read the judge’s compiler page, pick the entry your template was tested with, and use long long or
std::int64_t for 64-bit values, which judges on Windows also need.
Checkpoint
Check: Matching an online judge
Six habits remove the four breaks
Each of the four breaks has a habit that removes it, at the cost of a line or two:
| Where it breaks | The habit |
|---|---|
| A header of one library | Explicit headers, such as <vector> and <cstdint>, exist in every standard library |
| A variable-length array | std::vector replaces a variable-length array everywhere |
An extension such as __int128 |
Feature tests, such as #if defined(__SIZEOF_INT128__), keep an extension where it exists |
long as a 64-bit type |
Fixed-width types, such as std::int64_t, have the same size on every platform |
Bytes in plain char |
unsigned char gives byte values from 0 to 255 on every platform |
| Features in different releases | A CI matrix builds and tests the code with several compilers on several systems |
Checkpoint
Check: Habits that keep code portable
Check yourself
Nine questions follow, on gcc_only.cpp and its four results, the types models.cpp checks, the judges’ compilers
and the six habits, starting from your weakest topic, with a reason under every choice.
Practice that adapts to you
Practise this lesson
9 questions about 8 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.
Porting the GCC-only program, diagnostic by diagnostic Pro: opens with a Pro, Premium or Ultimate pass
A port starts from the full list of diagnostics, not the first one. This part collects every message from four compilers, ports the program line by line until all of them build it cleanly, and measures a classic contest shortcut with five compilers.
In this part:
- Every diagnostic, compiler by compiler
- The port, line by line
- 64-bit modular multiplication with five compilers, measured
1 diagram · 8 runnable examples (C++, Python, Text) · about 7 minutes
Opening this part with your pass…
This part opens with any MySmartCoPilot pass.
- Pro pass · 1 week₹79$2.99
- 30 days₹179$5.99
- 1 year₹1,699$45
One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.
Passes can’t be bought right now, so this part can’t be opened at the moment. The free part above stands on its own. If you have a pass, enter your licence key.
It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).
Differences that compile everywhere, and a CI matrix that catches them Pro: opens with a Pro, Premium or Ultimate pass
Some differences produce no error at all: the program builds on every system and prints different answers. This part reproduces two of them on three systems and turns the whole lesson into a CI matrix.
In this part:
- Plain char’s sign, reproduced on three systems
- int64_t is long on Linux and long long elsewhere
- A CI matrix that catches all of this
1 diagram · 2 runnable examples (C++) · about 6 minutes
Opening this part with your pass…Pro part. It opens with the same pass as the part above.
Interview questions
-
Fresher · Warm-up
Why does long have 4 bytes on Windows but 8 bytes on 64-bit Linux and macOS?
The C++ standard only says that long is at least 32 bits and no smaller than int; each platform's ABI picks the exact size. 64-bit Windows keeps long at 4 bytes and makes only long long and pointers 8 bytes, a model called LLP64, and every Windows compiler follows it, MSVC and MinGW GCC alike. 64-bit Linux and macOS make long 8 bytes as well, the LP64 model. So code that needs 64 bits must not say long: std::int64_t and std::uint64_t from <cstdint> have exactly 64 bits everywhere, and long long has at least 64. A sum of about 4 billion stored in a long works on Linux and overflows on Windows, as this lesson's MinGW run showed.
-
Fresher to mid
Your contest template uses <bits/stdc++.h>, __int128 to compute (a * b) % m for m up to 10^18, and long for 64-bit counts. You must now build it with MSVC on Windows and test it on a Mac. What breaks, and how do you port it without losing correctness?
-
Mid to senior
A string hash function gives different values on an Arm64 Linux server than on x86 laptops and Macs, so cache keys stop matching. The code compiles without warnings on every machine. What is a likely cause, how do you prove it, and how do you fix it?
-
Mid to senior
Your open-source C++ library promises to support GCC, Clang and MSVC on Linux, macOS and Windows, but each pull request may use about 30 minutes of CI time. Design the matrix: which jobs, with which flags, and what each one is there to catch.
Key takeaways
<bits/stdc++.h>belongs to libstdc++: include the standard headers you use.longhas 4 bytes on Windows; usestd::int64_tandstd::uint64_tfor 64-bit values.- Plain
charis unsigned on Arm64 Linux, andlong doubleis justdoubleon Apple silicon and with MSVC. - Keep extensions such as
__int128behind feature tests, and build on GCC, Clang and MSVC in CI.
Terms in this lesson
- compiler extension
- A feature a compiler accepts beyond the C++ standard, such as GCC's __int128 or variable-length arrays. Code that relies on one builds only with the compilers that offer it, so portable code keeps it behind a feature test.
- plain char
- The type char written without signed or unsigned: a type of its own whose sign each platform chooses. It is signed on x86 and on Apple's arm64 platforms, and unsigned on Arm64 Linux, so bytes above 0x7F compare differently.
References
- Data Type Ranges (Microsoft C++ documentation) (Microsoft)
- Microsoft C/C++ language conformance by Visual Studio version (Microsoft)
- Headers (libstdc++ manual) (GNU Project)
- Linking (libstdc++ manual, Using) (GNU Project)
- Writing ARM64 code for Apple platforms (Apple)
- Procedure Call Standard for the Arm 64-bit Architecture (AAPCS64) (Arm)
- _umul128 (Microsoft C++ documentation) (Microsoft)
- What are the environments for the programming languages? (LeetCode help centre, archived copy) (LeetCode)
- AtCoder language list (2025 language update) (AtCoder)
- _udiv128 (Microsoft C++ documentation) (Microsoft)
- About billing for GitHub Actions (GitHub)
- GitHub-hosted runners reference (GitHub)
- Compiler Explorer (Compiler Explorer project)
Related tools
Report a problem with this lesson
Kept only in this browser. Your Learn progress