Portability macros: detectando plataformas em compile-time
1. Por que detectar plataformas em C?
1.1. Diferenças entre sistemas operacionais (POSIX vs Windows)
C é uma linguagem portável por definição, mas o código-fonte frequentemente precisa lidar com APIs específicas de cada sistema operacional. POSIX (Portable Operating System Interface) define padrões para sistemas Unix-like, enquanto Windows possui sua própria API Win32. Essas diferenças afetam chamadas de sistema, manipulação de arquivos, threads e gerenciamento de memória.
1.2. Variações de arquitetura de CPU (x86, ARM, RISC-V)
Cada arquitetura de processador impõe regras diferentes sobre alinhamento de memória, tamanho de tipos básicos e endianness. Código que funciona em x86 pode falhar em ARM devido a restrições de alinhamento.
1.3. Problemas comuns
Os problemas mais frequentes incluem:
- Chamadas de sistema inexistentes em determinada plataforma (ex: fork() no Windows)
- Tipos de dados com tamanhos diferentes (long tem 8 bytes no Linux x86-64, 4 bytes no Windows x86-64)
- Alinhamento de memória mais restritivo em arquiteturas como ARM
- Endianness diferente entre plataformas (big-endian vs little-endian)
2. Macros de plataforma fornecidas pelo compilador
2.1. Macros POSIX padrão
#define _POSIX_C_SOURCE 200809L
#define _XOPEN_SOURCE 700
_POSIX_C_SOURCE permite usar funcionalidades POSIX específicas por versão. _XOPEN_SOURCE habilita extensões X/Open e UNIX.
2.2. Macros específicas do Linux
#ifdef __linux__
/* Código específico para Linux */
#endif
#ifdef __gnu_linux__
/* Código específico para Linux com glibc */
#endif
2.3. Macros do Windows
#ifdef _WIN32
/* Código para Windows 32-bit e 64-bit */
#endif
#ifdef _WIN64
/* Código específico para Windows 64-bit */
#endif
#ifdef __MINGW32__
/* Código para MinGW (GCC no Windows) */
#endif
3. Detectando o compilador e sua versão
3.1. GCC e Clang
#if defined(__GNUC__) && !defined(__clang__)
/* Compilador GCC */
#if __GNUC__ >= 8
/* GCC versão 8 ou superior */
#endif
#endif
#ifdef __clang__
/* Compilador Clang */
#if __clang_major__ >= 10
/* Clang versão 10 ou superior */
#endif
#endif
3.2. MSVC
#ifdef _MSC_VER
/* Microsoft Visual C++ */
#if _MSC_VER >= 1920
/* MSVC 2019 ou superior (versão 16.x) */
#endif
#endif
3.3. Compiladores menos comuns
#ifdef __TINYC__
/* Tiny C Compiler */
#endif
#ifdef __ICC
/* Intel C Compiler */
#endif
4. Identificando a arquitetura do processador
4.1. x86/x86-64
#if defined(__i386__) || defined(_M_IX86)
/* x86 32-bit */
#elif defined(__x86_64__) || defined(_M_X64)
/* x86-64 64-bit */
#endif
4.2. ARM
#if defined(__arm__) || defined(_M_ARM)
/* ARM 32-bit */
#elif defined(__aarch64__) || defined(_M_ARM64)
/* ARM 64-bit (AArch64) */
#endif
4.3. Outras arquiteturas
#ifdef __mips__
/* MIPS */
#endif
#ifdef __powerpc__
/* PowerPC */
#endif
#ifdef __riscv
/* RISC-V */
#endif
5. Macros para tamanho de tipos e endianness
5.1. Tamanho de ponteiro
#if defined(__SIZEOF_POINTER__)
#if __SIZEOF_POINTER__ == 8
/* Arquitetura 64-bit */
#elif __SIZEOF_POINTER__ == 4
/* Arquitetura 32-bit */
#endif
#elif defined(UINTPTR_MAX)
#if UINTPTR_MAX == 0xFFFFFFFFFFFFFFFF
/* Arquitetura 64-bit */
#elif UINTPTR_MAX == 0xFFFFFFFF
/* Arquitetura 32-bit */
#endif
#endif
5.2. Detecção de endianness
#if defined(__BYTE_ORDER__)
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
/* Little-endian */
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
/* Big-endian */
#endif
#elif defined(__LITTLE_ENDIAN__) || defined(_LITTLE_ENDIAN)
/* Little-endian */
#elif defined(__BIG_ENDIAN__) || defined(_BIG_ENDIAN)
/* Big-endian */
#endif
5.3. Macros de alinhamento
/* GCC/Clang */
#define ALIGNED(x) __attribute__((aligned(x)))
/* MSVC */
#ifdef _MSC_VER
#define ALIGNED(x) __declspec(align(x))
#endif
6. Estratégias práticas com #ifdef e #if
6.1. Encadeamento lógico
#if defined(__linux__) || defined(__unix__)
/* Código Unix-like */
#elif defined(_WIN32)
/* Código Windows */
#else
#error "Plataforma não suportada"
#endif
6.2. Criação de macros unificadas
#if defined(__linux__) || defined(__gnu_linux__)
#define PLATFORM_LINUX 1
#elif defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS 1
#elif defined(__APPLE__) && defined(__MACH__)
#define PLATFORM_MACOS 1
#endif
6.3. Uso de #else e #elif para fallback seguro
#ifdef _WIN32
#include <windows.h>
#define SLEEP(ms) Sleep(ms)
#elif defined(__unix__) || defined(__APPLE__)
#include <unistd.h>
#define SLEEP(ms) usleep((ms) * 1000)
#else
#define SLEEP(ms) /* implementação alternativa */
#endif
7. Boas práticas e armadilhas comuns
7.1. Evitar macros não documentadas
Prefira macros documentadas oficialmente. Macros experimentais podem mudar entre versões do compilador sem aviso.
7.2. Testar com múltiplos compiladores em CI
Configure pipelines de CI (Continuous Integration) para testar o código em GCC, Clang e MSVC, em diferentes plataformas.
7.3. Documentar cada macro condicional
/*
* PLATFORM_LINUX: definido quando compilando para Linux
* PLATFORM_WINDOWS: definido quando compilando para Windows
* PLATFORM_MACOS: definido quando compilando para macOS
*/
8. Exemplo integrado: cabeçalho de portabilidade reutilizável
/* platform.h - Cabeçalho de portabilidade */
#ifndef PLATFORM_H
#define PLATFORM_H
/* 1. Detecção de plataforma */
#if defined(__linux__) || defined(__gnu_linux__)
#define PLATFORM_LINUX 1
#elif defined(_WIN32) || defined(_WIN64)
#define PLATFORM_WINDOWS 1
#elif defined(__APPLE__) && defined(__MACH__)
#define PLATFORM_MACOS 1
#else
#error "Plataforma não suportada"
#endif
/* 2. Detecção de arquitetura */
#if defined(__x86_64__) || defined(_M_X64)
#define ARCH_X86_64 1
#elif defined(__i386__) || defined(_M_IX86)
#define ARCH_X86 1
#elif defined(__aarch64__) || defined(_M_ARM64)
#define ARCH_ARM64 1
#elif defined(__arm__) || defined(_M_ARM)
#define ARCH_ARM 1
#endif
/* 3. Detecção de compilador */
#if defined(__clang__)
#define COMPILER_CLANG 1
#elif defined(__GNUC__)
#define COMPILER_GCC 1
#elif defined(_MSC_VER)
#define COMPILER_MSVC 1
#endif
/* 4. Tipos de inteiros de largura fixa */
#include <stdint.h>
/* 5. Mapeamento de chamadas de sistema */
#if PLATFORM_WINDOWS
#include <windows.h>
#define PLATFORM_SLEEP(ms) Sleep(ms)
#else
#include <unistd.h>
#define PLATFORM_SLEEP(ms) usleep((ms) * 1000)
#endif
/* 6. Função de limpeza de tela */
#if PLATFORM_WINDOWS
#define CLEAR_SCREEN() system("cls")
#else
#define CLEAR_SCREEN() system("clear")
#endif
/* 7. Alinhamento de memória */
#if COMPILER_GCC || COMPILER_CLANG
#define ALIGNED(x) __attribute__((aligned(x)))
#elif COMPILER_MSVC
#define ALIGNED(x) __declspec(align(x))
#else
#define ALIGNED(x)
#endif
/* 8. Atributo de não retorno */
#if COMPILER_GCC || COMPILER_CLANG
#define NORETURN __attribute__((noreturn))
#elif COMPILER_MSVC
#define NORETURN __declspec(noreturn)
#else
#define NORETURN
#endif
#endif /* PLATFORM_H */
Exemplo de uso:
/* main.c */
#include "platform.h"
#include <stdio.h>
int main(void) {
printf("Plataforma detectada: ");
#if PLATFORM_LINUX
printf("Linux\n");
#elif PLATFORM_WINDOWS
printf("Windows\n");
#elif PLATFORM_MACOS
printf("macOS\n");
#endif
printf("Aguardando 2 segundos...\n");
PLATFORM_SLEEP(2000);
CLEAR_SCREEN();
printf("Tela limpa!\n");
return 0;
}
Referências
- GCC Preprocessor Manual - System-specific Predefined Macros — Documentação oficial do GCC sobre macros predefinidas específicas de sistema.
- Clang Language Extensions - Built-in Macros — Referência completa das macros predefinidas do Clang para detecção de plataforma e arquitetura.
- Microsoft Visual C++ Predefined Macros — Lista oficial da Microsoft com todas as macros predefinidas do MSVC.
- POSIX Base Definitions - _POSIX_C_SOURCE — Especificação oficial POSIX sobre o uso da macro _POSIX_C_SOURCE.
- Pre-defined Compiler Macros Wiki — Repositório colaborativo abrangente sobre macros de compilador, plataforma e arquitetura.
- GNU C Library - Feature Test Macros — Documentação da glibc sobre macros de teste de funcionalidades para portabilidade.
- C Programming - Portability and Preprocessor — Tutorial prático sobre o pré-processador C e técnicas de portabilidade.