Appearance
Portability, ABIs, and Platform Differences
Portable source can be rebuilt for several targets. Binary-compatible code can exchange calls and data without rebuilding. These are separate promises, and C++ libraries often achieve the first more easily than the second.
Data models change widths
Common 64-bit systems use different models:
- LP64:
longand pointers are 64-bit; common on Unix-like systems. - LLP64:
longremains 32-bit while pointers are 64-bit; common on 64-bit Windows.
Never store a pointer in long by assumption. Use intptr_t/uintptr_t when an integer representation is available and genuinely required. Use size_t for object sizes and explicit fixed-width types for serialized data.
Byte order also varies. Network and file formats should choose an order and convert fields explicitly. Raw host structures are not formats.
Object formats and naming differ
ELF is common on Linux and other systems, Mach-O on Apple platforms, and PE/COFF on Windows. Tools, section rules, exported-symbol conventions, and dynamic loader metadata differ.
C++ overloads require encoded—mangled—symbol names. Compilers sharing a platform ABI may agree; unrelated ABI families need not. extern "C" provides C language linkage for a C-compatible interface but does not standardize struct packing, runtime allocation, or calling convention across every target.
Calling conventions and runtimes are contracts
An ABI defines how arguments and returns use registers/stack, how types are laid out, how exceptions unwind, and more. Platform architecture ABIs standardize much of this, while compiler/runtime options can add constraints.
Do not pass a C++ standard-library type across an ABI boundary unless producer and consumer share a supported compiler/library ABI contract. Prefer opaque handles, fixed-width scalars, pointer-plus-length buffers, explicit ownership, and functions to create/destroy objects.
Symbol visibility is part of library design
Unix-like compilers can hide symbols by default and explicitly export public API. Windows DLLs commonly use import/export annotations. CMake can generate export macros, but the library still needs an intentional public surface.
Fewer exported symbols reduce accidental ABI commitments and link/load collisions. An inline function or template in a public header instead becomes source/instantiation coupling and may still expose layout or library ABI.
POSIX and Windows need architectural seams
Processes, sockets, path encodings, dynamic loading, file permissions, terminals, and event mechanisms differ. Build thin platform adapters that expose domain-shaped operations. Avoid a lowest-common-denominator wrapper so vague that error and capability differences disappear.
Use filesystem abstractions for paths, but understand encoding: Win32 wide APIs conventionally use 16-bit wchar_t code units for UTF-16, while Unix-like paths are byte sequences with platform conventions. One Windows wchar_t therefore need not represent a complete Unicode scalar value. User-facing text and filesystem identity are not always interchangeable.
Cross-compilation separates build, host, and target
The build system runs on the build machine; tools it produces may run on the host; final artifacts run on the target. In ordinary native compilation all can coincide. Cross builds need a target compiler, sysroot, libraries, and a way to avoid executing target binaries during configuration unless an emulator is provided.
CMake toolchain files and package-manager profiles record these choices. A target library discovered from the host system is a dangerous false success.
Define tiers of portability
A project might promise:
- source portability across GCC/Clang/MSVC on named OS versions;
- a stable C ABI within one architecture and major release;
- serialized-data compatibility across endian and width differences;
- no binary compatibility for C++ internals;
- a freestanding subset for embedded targets.
Test each promise directly. A Linux-only CI job cannot validate Windows path or loader behavior.
Optional prompts
Explain: Why is long unsuitable for a portable 64-bit serialized field?
Answer: Its width differs between common LP64 and LLP64 systems. Use an explicit-width representation and defined byte order.
Explain: Does a stable C API guarantee allocation can cross any compiler boundary?
Answer: No. The interface must also specify matching allocator/deallocator functions and compatible target/runtime assumptions.