Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

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.

  • Intermediate
  • 10 minutes
  • Examples run with Clang 21.0.0, GCC 16.2.0 and Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 4 of 10 minutes free
Free: One program gets four answers from four compilers · long, plain char and long double change with the target · Online judges publish the compiler you must match · 2 more sections. With a pass: 2 in-depth parts and 3 interview answers, about 6 minutes.

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:

gcc_only.cpp C++ · gcc_only.cpp
// 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:

Apple clang and GCC 16 on macOS Python · gcc_only_session.py
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:

MSVC 19.51 and MinGW GCC 16.2.0 on Windows (recorded) Text · windows_gcc_only.txt
$ 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

  1. Question 1 of 2 Which standard library provides <bits/stdc++.h>?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: libstdc++, GCC's standard library

    <bits/stdc++.h> is a file of libstdc++. Clang finds it when it uses libstdc++, as on most Linux systems, and not when it uses libc++.

  2. Question 2 of 2 MinGW GCC built gcc_only.cpp with two warnings and printed a total of -76856817. Why is that the most dangerous of the four results?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: There is no error, only a wrong number: a 4-byte long overflowed at run time

    A fatal error stops you at once. A build that succeeds and prints a wrong answer goes on to be trusted, which is why the Windows result needs a test, not just a build.

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:

models.cpp C++ · models.cpp
// 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");
The same checks for five targets Python · models_session.py
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

  1. Question 1 of 2 models.cpp was compiled for x86_64-pc-windows-msvc. Which of its static_asserts failed?

    Choose every answer that is right.

    Show the answer: Question 1 of 2

    Answer:

    • long double is no wider than double here
    • long is not 64 bits here

    The Windows target failed two of the three checks, sizeof(long) == 8 and a wider long double, as the table shows: 4-byte long, signed char, long double the same as double.

  2. Question 2 of 2 Which type gives byte values from 0 to 255 on every platform?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: unsigned char

    The sign of plain char is the platform's choice: signed on x86 and on Apple's arm64 platforms, unsigned on Arm64 Linux. unsigned char takes that choice away.

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

  1. One question Your contest template starts with #include <bits/stdc++.h>. Which judge entry of the table rejects it?

    Choose one answer.

    Show the answer: One question

    Answer: AtCoder's Clang entry, which builds with -stdlib=libc++

    The compiler's name says little; the library decides. Read the judge's compiler page and pick the entry your template was tested with.

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

  1. One question Your code uses __int128 where the compiler has it. Which habit keeps it building everywhere?

    Choose one answer.

    Show the answer: One question

    Answer: A feature test, #if defined(__SIZEOF_INT128__), so the extension is used only where it exists

    Extensions stay useful behind feature tests. The habit table pairs each of the four breaks with the line or two that removes it.

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.

  1. Question 1 of 9 Which compiler on the Mac rejected gcc_only.cpp at once?

    Choose one answer.

    Show the answer: Question 1 of 9

    Answer: Apple clang, because libc++ has no bits/stdc++.h

    Apple clang uses libc++, which has no <bits/stdc++.h>. GCC 16 on the same Mac uses libstdc++ and built the program with two warnings.

  2. Question 2 of 9 Why did Clang on Linux compile

    Choose one answer.

    Show the answer: Question 2 of 9

    Answer: The header belongs to libstdc++, which Clang uses on that Linux system, while Apple clang uses libc++

    bits/stdc++.h is a file of GCC's standard library, libstdc++. The same Clang does not find it when it uses libc++ instead.

  3. Question 3 of 9 A program stores a sum of about 4 billion in a long. On which platform does it overflow?

    Choose one answer.

    Show the answer: Question 3 of 9

    Answer: Windows, with MSVC or MinGW, where long has 4 bytes

    Windows uses 4-byte long with every compiler, so the largest value is 2,147,483,647. MinGW's run of gcc_only.cpp printed -76856817 for a total of 4,218,110,479. Use std::int64_t for 64-bit values.

  4. Question 4 of 9 On which target is if (c < 0), for a plain char c, never true?

    Choose one answer.

    Show the answer: Question 4 of 9

    Answer: Linux on Arm64, where plain char is unsigned

    Arm's procedure call standard makes plain char unsigned, so a char there holds 0 to 255; Apple chose a signed char for its arm64 platforms. On x86 and with MSVC it is signed too.

  5. Question 5 of 9 You compile models.cpp for one more target, and only the long double assertion fails. Which target of the table behaves like that?

    Choose one answer.

    Show the answer: Question 5 of 9

    Answer: macOS on Apple silicon

    Read the target from the table: only macOS on Apple silicon has an 8-byte long, a signed char and a long double that is the same as double.

  6. Question 6 of 9 Which compiler and library does LeetCode publish for C++?

    Choose one answer.

    Show the answer: Question 6 of 9

    Answer: Clang 19 with libstdc++ from GCC 14

    LeetCode compiles with Clang 19 and libstdc++ from GCC 14, at -O2 with AddressSanitizer, and includes most standard headers for you, so <bits/stdc++.h> habits go unnoticed there.

  7. Question 7 of 9 Your template keeps 64-bit values in long. What does the lesson tell you to use instead, so that judges on Windows are safe too?

    Choose one answer.

    Show the answer: Question 7 of 9

    Answer: long long or std::int64_t

    A judge on Windows gives long 4 bytes like every Windows compiler. long long or std::int64_t keeps a 64-bit value 64 bits wide everywhere.

  8. Question 8 of 9 What replaces a variable-length array in portable C++?

    Choose one answer.

    Show the answer: Question 8 of 9

    Answer: std::vector

    The habit table pairs the variable-length array with std::vector: one line, and the same code for GCC, Clang and MSVC.

  9. Question 9 of 9 A function keeps a 64-bit count in long, and its tests pass on Linux and macOS. Which change makes it right on Windows too, with every compiler?

    Choose one answer.

    Show the answer: Question 9 of 9

    Answer: Declare the count as std::int64_t

    long is the type whose size the platform chooses. std::int64_t names the size you need, so the count is 64 bits on Linux, macOS and Windows alike.

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.

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

  1. 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.

  2. 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?

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  3. 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?

    Model answer, Pro. First step and follow-ups

  4. 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.

    Model answer, Pro. First step and follow-ups

Key takeaways

  • <bits/stdc++.h> belongs to libstdc++: include the standard headers you use.
  • long has 4 bytes on Windows; use std::int64_t and std::uint64_t for 64-bit values.
  • Plain char is unsigned on Arm64 Linux, and long double is just double on Apple silicon and with MSVC.
  • Keep extensions such as __int128 behind 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

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.