Skip to content
Portability Issues in C/C++

Portability Issues in C/C++

1. Data Type Sizes

The Problem

Integer and pointer sizes vary across different platforms.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Non-portable assumptions
int x = ptr;  // Assuming a pointer fits in an int (fails on 64-bit systems)
long l = 5L;  // long is 32-bit on Windows 64, but 64-bit on Unix

// ✅ Portable solutions
#include <stdint.h>
int32_t x;      // Exactly 32 bits
uint64_t y;     // Exactly 64 bits
intptr_t ptr_i; // Integer capable of holding a pointer
size_t sz;      // Used for sizes

2. Endianness

The Problem

Different CPUs store multi-byte values in different ways.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ❌ Non-portable
union {
    uint32_t i;
    uint8_t bytes[4];
} data;
data.i = 0x12345678;
// bytes[0] is 0x78 (Little Endian) or 0x12 (Big Endian)

// ✅ Portable byte extraction
uint32_t value = 0x12345678;
uint8_t byte0 = (value >> 24) & 0xFF;  // Always 0x12
uint8_t byte3 = value & 0xFF;           // Always 0x78

// ✅ Network byte order conversion
#include <arpa/inet.h>  // Unix
uint32_t net = htonl(host_value);  // Host to Network order (Big Endian)
uint32_t host = ntohl(net_value);  // Network to Host order

3. Compiler-Specific Extensions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ❌ Non-portable (GCC/Clang specific)
int array[0];              // Zero-length arrays
typeof(x) y;               // typeof operator
__attribute__((packed));   // attributes

// ✅ Portable alternatives
#ifdef __GNUC__
    __attribute__((packed))
#elif defined(_MSC_VER)
    __declspec(align(1))
#endif

// Using Standards
#include <stdalign.h>  // C11
alignas(16) int x;

4. Operating System Differences

File Paths

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Platform specific
#define PATH "C:\\Users\\file.txt"     // Windows only
#define PATH "/home/user/file.txt"     // Unix only

// ✅ Runtime construction
#ifdef _WIN32
    const char *sep = "\\";
#else
    const char *sep = "/";
#endif

System APIs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Platform specific APIs
#include <windows.h>
HANDLE h = CreateFile(...);  // Windows only

#include <unistd.h>
int fd = open(...);          // POSIX only

// ✅ Use abstractions or cross-platform libraries
// Standard C FILE* or C++ fstream
FILE *f = fopen("file.txt", "r");

5. Character Sets and Encodings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Assuming ASCII
char c = 'A';
if (c == 65) { ... }  // Fragile

// ✅ Character-independent encoding
if (c == 'A') { ... }

// Wide characters for Unicode
#include <wchar.h>
wchar_t wstr[] = L"Hello 世界";

6. Struct Padding and Alignment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ❌ Assuming no padding
struct Data {
    char c;    // 1 byte
    int i;     // 4 bytes
};
// Size could be 5, 8, or others, depending on the platform!

// ✅ Mind the padding
size_t size = sizeof(struct Data);  // Use sizeof, do not assume

// Force packing (use with caution)
#pragma pack(push, 1)
struct Packed {
    char c;
    int i;
} __attribute__((packed));  // GCC
#pragma pack(pop)

7. Pointer Issues

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ❌ Pointer/Integer confusion
int *p = (int *)0x1000;  // Assuming address space
int i = (int)p;          // Truncation on 64-bit

// ✅ Correct pointer handling
#include <stdint.h>
uintptr_t addr = (uintptr_t)p;  // Safe conversion

// ❌ Pointer arithmetic assumptions
char *p1 = malloc(10);
char *p2 = malloc(10);
ptrdiff_t diff = p1 - p2;  // Undefined if not from the same allocation!

8. Signed vs Unsigned

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ❌ Implicit sign issues
char c = 200;  // Implementation-defined: signed char or unsigned char?
if (c > 0) { ... }  // Might be false if char is signed!

// ✅ Explicit signness
unsigned char uc = 200;
signed char sc = 100;

// ❌ Dangerous comparisons
int i = -1;
unsigned int u = 1;
if (i < u) { ... }  // Error! i is converted to unsigned

9. Undefined and Unspecified Behavior

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Undefined Behavior (varies across compilers)
int i = 0;
i = i++;              // Undefined behavior
int x = (1 << 31);    // Undefined for signed int
int *p = NULL;
*p = 5;               // Undefined

// Unspecified: Evaluation order
f() + g();            // Order of f() and g() is not guaranteed
printf("%d %d", i++, i++);  // Unspecified order

10. Floating-point Portability

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ❌ Assuming IEEE 754
float f = 0.1;
if (f == 0.1) { ... }  // Might fail due to precision

// ✅ Epsilon comparison
#include <float.h>
#include <math.h>
if (fabs(f - 0.1) < FLT_EPSILON) { ... }

// Portable checks for special values
if (isnan(x)) { ... }
if (isinf(x)) { ... }

11. Line Endings

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ❌ Text mode varies by platform
FILE *f = fopen("file.txt", "r");  // \n on Unix, \r\n on Windows

// ✅ Binary mode for consistency
FILE *f = fopen("file.txt", "rb");  // Consistent across platforms

// Or handle explicitly
while ((c = fgetc(f)) != EOF) {
    if (c == '\r' || c == '\n') { ... }
}

12. Preprocessor Differences

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// ❌ Non-portable predefined macros
#ifdef WIN32  // Some compilers use _WIN32

// ✅ Check for multiple variants
#if defined(_WIN32) || defined(_WIN64) || defined(__WINDOWS__)
    // Windows code
#elif defined(__linux__)
    // Linux code
#elif defined(__APPLE__)
    // macOS code
#endif

13. Standard Library Variants

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// ❌ Platform specific functions
char *str = strdup(s);   // Not C standard (POSIX)
strlcpy(dst, src, n);    // BSD, not universal

// ✅ Standard alternatives
char *str = malloc(strlen(s) + 1);
if (str) strcpy(str, s);

// Or provide a fallback
#ifndef HAVE_STRDUP
char *strdup(const char *s) {
    char *p = malloc(strlen(s) + 1);
    return p ? strcpy(p, s) : NULL;
}
#endif

14. Integer Promotion and Conversion

1
2
3
4
5
6
7
8
// ❌ Implicit conversions
uint8_t a = 200, b = 100;
uint8_t c = a + b;  // Overflow! Promoted to int, then truncated

// ✅ Explicit typing
uint8_t c = (uint8_t)((unsigned)a + b);
// Or use wider types
unsigned int result = (unsigned)a + b;

15. Calling Conventions

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// ❌ Assuming specific calling conventions
void __cdecl func();     // MSVC specific
void __stdcall func2();  // Windows specific

// ✅ Use standard declarations or abstractions
void func(void);

// For DLLs, use macros
#ifdef _WIN32
    #define EXPORT __declspec(dllexport)
#else
    #define EXPORT __attribute__((visibility("default")))
#endif

Best Practices for Portable Code

1. Use Standard Headers

1
2
3
4
#include <stdint.h>   // Fixed-width integers
#include <stddef.h>   // size_t, ptrdiff_t
#include <stdbool.h>  // bool type (C99)
#include <limits.h>   // INT_MAX, etc.

2. Feature Testing

1
2
3
4
5
6
7
8
9
#if __STDC_VERSION__ >= 201112L
    // C11 features available
#endif

#ifdef __cplusplus
    // C++ code
#else
    // C code
#endif

3. Calculate Sizes with sizeof

1
2
3
4
5
6
// ❌ Never hardcode sizes
int array[10];
memset(array, 0, 40);  // Error on some platforms!

// ✅ Always use sizeof
memset(array, 0, sizeof(array));

4. Configuration Headers

1
2
3
4
5
6
7
8
// config.h (generated by build system)
#ifdef HAVE_UNISTD_H
    #include <unistd.h>
#endif

#ifdef HAVE_SYS_TYPES_H
    #include <sys/types.h>
#endif

5. Cross-Platform Libraries

  • Use libraries like Boost (C++), GLib, APR, etc.
  • Abstract OS-specific functionality.
  • Use CMake for build portability.

Key Takeaway: Write code that conforms to standards (C99/C11/C++11/14/17/20), avoid assumptions about implementation details, test on multiple platforms, and use abstraction layers for OS-specific features.

Last updated on