跨平台编程
00:00
C语言跨平台开发
概述
C语言的一大优势是可移植性,但不同操作系统和硬件平台之间存在差异:文件路径分隔符、线程API、网络接口、字节序等。跨平台编程的目标是编写一套代码,在多个平台上无需修改即可编译运行。这需要合理使用条件编译、平台抽象层和可移植的库函数。
基础概念
平台差异的主要来源
- 操作系统API不同(Windows vs Linux vs macOS)
- 编译器扩展不同(GCC vs Clang vs MSVC)
- 字节序不同(大端 vs 小端)
- 数据类型大小不同(32位 vs 64位)
- 路径分隔符不同(
/vs\)
常用预定义宏
| 宏 | 说明 |
|---|---|
_WIN32 | Windows(32位和64位都定义) |
_WIN64 | Windows 64位 |
__linux__ | Linux |
__APPLE__ | macOS/iOS |
__unix__ | Unix-like系统 |
__GNUC__ | GCC编译器 |
__clang__ | Clang编译器 |
_MSC_VER | MSVC编译器版本 |
__x86_64__ | x86-64架构 |
__aarch64__ | ARM64架构 |
快速上手
平台检测与条件编译
#include <stdio.h>
int main(void) {
// 检测操作系统
#if defined(_WIN32)
printf("操作系统: Windows\n");
#elif defined(__linux__)
printf("操作系统: Linux\n");
#elif defined(__APPLE__)
printf("操作系统: macOS\n");
#elif defined(__unix__)
printf("操作系统: Unix\n");
#else
printf("操作系统: 未知\n");
#endif
// 检测编译器
#if defined(__GNUC__)
printf("编译器: GCC %d.%d\n", __GNUC__, __GNUC_MINOR__);
#elif defined(__clang__)
printf("编译器: Clang %d.%d\n", __clang_major__, __clang_minor__);
#elif defined(_MSC_VER)
printf("编译器: MSVC %d\n", _MSC_VER);
#endif
// 检测架构
#if defined(__x86_64__) || defined(_M_X64)
printf("架构: x86-64\n");
#elif defined(__aarch64__)
printf("架构: ARM64\n");
#elif defined(__i386__) || defined(_M_IX86)
printf("架构: x86\n");
#endif
return 0;
}
跨平台路径处理
#include <stdio.h>
#include <string.h>
// 路径分隔符
#ifdef _WIN32
#define PATH_SEPARATOR '\\'
#define PATH_SEPARATOR_STR "\\"
#else
#define PATH_SEPARATOR '/'
#define PATH_SEPARATOR_STR "/"
#endif
// 跨平台路径拼接
void join_path(char *buf, size_t buf_size, const char *dir, const char *file) {
size_t dir_len = strlen(dir);
// 去除末尾的分隔符
while (dir_len > 0 && (dir[dir_len - 1] == '/' || dir[dir_len - 1] == '\\')) {
dir_len--;
}
snprintf(buf, buf_size, "%.*s%c%s", (int)dir_len, dir, PATH_SEPARATOR, file);
}
int main(void) {
char path[256];
join_path(path, sizeof(path), "/home/user/", "document.txt");
printf("路径: %s\n", path);
join_path(path, sizeof(path), "C:\\Users\\test", "file.txt");
printf("路径: %s\n", path);
return 0;
}
详细用法
跨平台休眠函数
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <unistd.h>
#endif
// 跨平台毫秒级休眠
void sleep_ms(unsigned int milliseconds) {
#ifdef _WIN32
Sleep(milliseconds);
#else
usleep(milliseconds * 1000);
#endif
}
int main(void) {
printf("休眠1秒...\n");
sleep_ms(1000);
printf("休眠结束\n");
return 0;
}
跨平台目录创建
#include <stdio.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#define mkdir(path, mode) _mkdir(path)
#endif
// 跨平台创建目录
int create_directory(const char *path) {
#ifdef _WIN32
if (_mkdir(path) == 0) {
return 0;
}
// 目录已存在不算错误
return errno == EEXIST ? 0 : -1;
#else
if (mkdir(path, 0755) == 0) {
return 0;
}
return errno == EEXIST ? 0 : -1;
#endif
}
int main(void) {
if (create_directory("test_dir") == 0) {
printf("目录创建成功\n");
}
return 0;
}
跨平台字节序检测
#include <stdio.h>
#include <stdint.h>
// 检测字节序
int is_little_endian(void) {
uint16_t test = 0x0001;
return *(uint8_t *)&test == 1;
}
// 字节序转换
uint16_t swap16(uint16_t val) {
return (val << 8) | (val >> 8);
}
uint32_t swap32(uint32_t val) {
return ((val & 0xFF000000) >> 24) |
((val & 0x00FF0000) >> 8) |
((val & 0x0000FF00) << 8) |
((val & 0x000000FF) << 24);
}
// 主机序转网络序(小端系统需要转换)
uint32_t host_to_network32(uint32_t val) {
if (is_little_endian()) {
return swap32(val);
}
return val;
}
int main(void) {
printf("字节序: %s\n", is_little_endian() ? "小端" : "大端");
uint32_t test = 0x12345678;
printf("原始: 0x%08X\n", test);
printf("转换: 0x%08X\n", host_to_network32(test));
return 0;
}
跨平台动态库加载
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
typedef HMODULE lib_handle_t;
#else
#include <dlfcn.h>
typedef void *lib_handle_t;
#endif
// 加载动态库
lib_handle_t lib_load(const char *path) {
#ifdef _WIN32
return LoadLibraryA(path);
#else
return dlopen(path, RTLD_LAZY);
#endif
}
// 获取函数指针
void *lib_get_func(lib_handle_t handle, const char *name) {
#ifdef _WIN32
return (void *)GetProcAddress(handle, name);
#else
return dlsym(handle, name);
#endif
}
// 关闭动态库
void lib_close(lib_handle_t handle) {
#ifdef _WIN32
FreeLibrary(handle);
#else
dlclose(handle);
#endif
}
int main(void) {
#ifdef _WIN32
lib_handle_t lib = lib_load("user32.dll");
#else
lib_handle_t lib = lib_load("libm.so.6");
#endif
if (lib) {
printf("动态库加载成功\n");
lib_close(lib);
} else {
printf("动态库加载失败\n");
}
return 0;
}
常见场景
场景一:跨平台日志系统
#include <stdio.h>
#include <stdarg.h>
#include <time.h>
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
// 获取毫秒级时间戳
long long get_timestamp_ms(void) {
#ifdef _WIN32
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
long long t = ((long long)ft.dwHighDateTime << 32) | ft.dwLowDateTime;
return (t - 116444736000000000LL) / 10000;
#else
struct timeval tv;
gettimeofday(&tv, NULL);
return (long long)tv.tv_sec * 1000 + tv.tv_usec / 1000;
#endif
}
// 跨平台日志函数
void log_msg(const char *level, const char *fmt, ...) {
long long ts = get_timestamp_ms();
printf("[%s] [%lld] ", level, ts);
va_list ap;
va_start(ap, fmt);
vprintf(fmt, ap);
va_end(ap);
printf("\n");
}
int main(void) {
log_msg("INFO", "程序启动");
log_msg("DEBUG", "变量 x = %d", 42);
log_msg("ERROR", "文件 %s 不存在", "config.txt");
return 0;
}
场景二:跨平台互斥锁
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
typedef CRITICAL_SECTION mutex_t;
void mutex_init(mutex_t *m) { InitializeCriticalSection(m); }
void mutex_lock(mutex_t *m) { EnterCriticalSection(m); }
void mutex_unlock(mutex_t *m) { LeaveCriticalSection(m); }
void mutex_destroy(mutex_t *m) { DeleteCriticalSection(m); }
#else
#include <pthread.h>
typedef pthread_mutex_t mutex_t;
void mutex_init(mutex_t *m) { pthread_mutex_init(m, NULL); }
void mutex_lock(mutex_t *m) { pthread_mutex_lock(m); }
void mutex_unlock(mutex_t *m) { pthread_mutex_unlock(m); }
void mutex_destroy(mutex_t *m) { pthread_mutex_destroy(m); }
#endif
int shared_counter = 0;
mutex_t counter_mutex;
void increment_counter(void) {
mutex_lock(&counter_mutex);
shared_counter++;
mutex_unlock(&counter_mutex);
}
int main(void) {
mutex_init(&counter_mutex);
increment_counter();
increment_counter();
printf("计数器: %d\n", shared_counter);
mutex_destroy(&counter_mutex);
return 0;
}
注意事项
避免在头文件中使用平台特定代码
头文件应提供统一的接口,平台特定实现放在 .c 文件中:
// platform.h - 统一接口
void sleep_ms(unsigned int ms);
int create_directory(const char *path);
// platform_linux.c - Linux实现
// platform_windows.c - Windows实现
使用标准库优先
C标准库函数本身就是跨平台的,优先使用标准函数:
// 优先使用标准函数
FILE *fp = fopen("file.txt", "r"); // 跨平台
size_t len = strlen(str); // 跨平台
// 避免直接使用平台API
// HANDLE h = CreateFile(...); // 仅Windows
// int fd = open(...); // 仅POSIX
编译器差异
不同编译器对C标准的支持程度不同,使用 _Static_assert 检查编译器特性:
// 检查int大小
_Static_assert(sizeof(int) == 4, "int必须是4字节");
// 检查指针大小
#if UINTPTR_MAX == 0xFFFFFFFF
// 32位平台
#elif UINTPTR_MAX == 0xFFFFFFFFFFFFFFFF
// 64位平台
#endif
进阶用法
构建平台抽象层
// platform.h - 平台抽象层头文件
#ifndef PLATFORM_H
#define PLATFORM_H
#include <stdint.h>
// 固定宽度类型
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;
// 平台信息
#if defined(_WIN32)
#define PLATFORM_NAME "Windows"
#elif defined(__linux__)
#define PLATFORM_NAME "Linux"
#elif defined(__APPLE__)
#define PLATFORM_NAME "macOS"
#endif
// 导出函数声明
#ifdef _WIN32
#define EXPORT __declspec(dllexport)
#else
#define EXPORT __attribute__((visibility("default")))
#endif
// 对齐属性
#ifdef _WIN32
#define ALIGNED(x) __declspec(align(x))
#else
#define ALIGNED(x) __attribute__((aligned(x)))
#endif
// 线程局部存储
#ifdef _WIN32
#define THREAD_LOCAL __declspec(thread)
#else
#define THREAD_LOCAL __thread
#endif
// 平台API
void plat_sleep_ms(u32 ms);
u64 plat_timestamp_ms(void);
int plat_create_dir(const char *path);
#endif // PLATFORM_H
使用CMake管理跨平台构建
cmake_minimum_required(VERSION 3.20)
project(CrossPlatformApp C)
set(CMAKE_C_STANDARD 17)
# 根据平台选择源文件
set(PLATFORM_SOURCES)
if(WIN32)
list(APPEND PLATFORM_SOURCES src/platform_windows.c)
else()
list(APPEND PLATFORM_SOURCES src/platform_linux.c)
endif()
add_executable(myapp
src/main.c
${PLATFORM_SOURCES}
)
# 平台特定的链接库
if(WIN32)
target_link_libraries(myapp PRIVATE ws2_32)
else()
target_link_libraries(myapp PRIVATE pthread dl)
endif()