C++ 크로스 플랫폼 기초 완벽 가이드 | 플랫폼 감지·std::filesystem

C++ 크로스 플랫폼 기초 완벽 가이드 | 플랫폼 감지·std::filesystem

이 글의 핵심

C++ Windows에서 빌드한 게 Linux에서 안 돌아가는 문제부터. 플랫폼 감지 매크로, std::filesystem 경로 처리, 동적 로딩 추상화, MinGW·iOS·Android 툴체인, 흔한 에러, 베스트 프랙티스, 프로덕션 패턴까지 실전 코드로 정리합니다.

들어가며: “Windows에서 빌드한 게 Linux에서 안 돌아가요”

실제 겪는 문제 시나리오

크로스 플랫폼 C++ 프로젝트를 운영하면 플랫폼마다 다른 경로 구분자·라이브러리·API 때문에 빌드가 실패하거나, 한 OS에서만 동작하는 코드가 섞여 들어갑니다. 비유하면 “한 나라에서 쓰는 전기 플러그를 다른 나라로 가져가면 어댑터가 필요해요”처럼, OS별로 빌드·실행 환경이 다르기 때문에 플랫폼 감지·경로 추상화·동적 로딩·툴체인이 필요합니다.

flowchart TD
  subgraph wrong[❌ 플랫폼 의존 코드]
    W1[하드코딩 경로 config\file.txt]
    W2[Windows 전용 LoadLibrary]
    W3[플랫폼별 #include 누락]
    W4[한 OS에서만 빌드 성공]
  end
  subgraph right[✅ 크로스 플랫폼]
    R1[std::filesystem::path]
    R2[동적 로딩 추상화]
    R3[플랫폼 매크로 분기]
    R4[모든 타겟에서 빌드]
  end

이 글에서 다루는 것:

  • 문제 시나리오: 크로스 플랫폼 개발에서 겪는 실제 고통
  • 플랫폼 감지 매크로: _WIN32, __linux__, __APPLE__, iOS/Android 구분
  • std::filesystem: 경로 처리·파일 존재 확인·디렉터리 순회
  • 동적 로딩 추상화: LoadLibrary/dlopen 통합 인터페이스
  • 툴체인: MinGW·iOS·Android 빌드 설정
  • 자주 발생하는 에러와 해결법
  • 베스트 프랙티스프로덕션 패턴

요구 환경: C++17 이상, CMake 3.16+


실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.

목차

  1. 문제 시나리오: 크로스 플랫폼이 깨지는 순간
  2. 플랫폼 감지 매크로
  3. std::filesystem 크로스 플랫폼 경로
  4. 동적 로딩 추상화
  5. 완전한 크로스 플랫폼 예제
  6. 툴체인: MinGW·iOS·Android
  7. 자주 발생하는 에러와 해결법
  8. 베스트 프랙티스
  9. 프로덕션 패턴
  10. 구현 체크리스트

1. 문제 시나리오: 크로스 플랫폼이 깨지는 순간

시나리오 1: 경로 구분자로 인한 파일 열기 실패

문제: Windows에서 path/to/file.txt 같은 경로로 파일을 열면 std::ifstream이 실패합니다. Windows는 \를 구분자로 쓰지만, Linux/macOS는 /를 씁니다. 하드코딩된 "config\\settings.json"은 Linux에서 동작하지 않습니다.

해결: std::filesystem::path를 사용하면 OS가 자동으로 올바른 구분자를 사용합니다. /를 사용해도 Windows는 내부적으로 처리합니다.

시나리오 2: 동적 라이브러리 확장자 불일치

문제: 플러그인을 로드할 때 plugin.so로만 찾습니다. Windows에서는 plugin.dll, macOS에서는 plugin.dylib이 필요합니다. 확장자를 하드코딩하면 다른 OS에서 로드 실패합니다.

해결: 플랫폼별 매크로로 확장자를 분기하거나, CMAKE_SHARED_LIBRARY_SUFFIX를 사용합니다.

시나리오 3: “undefined reference to pthread” (Windows)

문제: Linux에서 -lpthread로 링크한 코드가 Windows에서 빌드됩니다. Windows에는 pthread가 없고, MSVC는 별도 설정이 필요합니다.

해결: CMake에서 find_package(Threads)로 플랫폼별 스레드 라이브러리를 자동 링크합니다.

시나리오 4: unistd.h 없음 (Windows)

문제: #include <unistd.h>가 있는 코드를 Windows(MSVC)에서 빌드하면 fatal error: 'unistd.h' file not found가 발생합니다.

해결: #if defined(_WIN32)로 분기하거나, std::filesystem·std::thread 등 표준 라이브러리로 대체합니다.

시나리오 5: MinGW로 빌드한 DLL을 MSVC 앱에서 로드 실패

문제: MinGW(GCC)와 MSVC는 C++ ABI·표준 라이브러리(libstdc++ vs MSVCRT)가 달라서, 서로 빌드한 바이너리를 섞어 쓰면 크래시가 발생합니다.

해결: C ABI로 경계를 만들거나, 같은 툴체인으로 빌드합니다. 플러그인·DLL은 extern "C"로 내보내는 것이 안전합니다.

시나리오 6: iOS/Android 크로스 컴파일 실패

문제: 데스크톱용 CMake로 iOS·Android 앱을 빌드하려 합니다. arm64·armv7 아키텍처, iOS SDK 경로, Android NDK 경로가 설정되지 않아 빌드가 실패합니다.

해결: CMake 툴체인 파일(-DCMAKE_TOOLCHAIN_FILE=<path>)로 iOS/Android 전용 컴파일러·SDK를 지정합니다.


2. 플랫폼 감지 매크로

컴파일러별 정의되는 매크로

플랫폼매크로비고
Windows (32/64)_WIN3264비트에서도 정의됨
Windows 64비트_WIN64_WIN32와 함께 정의
Linux__linux__
macOS__APPLE__, __MACH__iOS와 구분 필요
Android__ANDROID____linux__와 함께 정의
iOS__APPLE__ + TARGET_OS_IPHONETargetConditionals.h 필요

완전한 플랫폼 감지 헤더

// platform_detect.hpp — 모든 주요 플랫폼 감지
#ifndef PLATFORM_DETECT_HPP
#define PLATFORM_DETECT_HPP

#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS 1
    #define PLATFORM_NAME "Windows"
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    #if TARGET_OS_IPHONE
        #define PLATFORM_IOS 1
        #define PLATFORM_NAME "iOS"
    #elif TARGET_OS_SIMULATOR
        #define PLATFORM_IOS_SIMULATOR 1
        #define PLATFORM_NAME "iOS Simulator"
    #else
        #define PLATFORM_MACOS 1
        #define PLATFORM_NAME "macOS"
    #endif
#elif defined(__linux__)
    #if defined(__ANDROID__)
        #define PLATFORM_ANDROID 1
        #define PLATFORM_NAME "Android"
    #else
        #define PLATFORM_LINUX 1
        #define PLATFORM_NAME "Linux"
    #endif
#elif defined(__FreeBSD__)
    #define PLATFORM_FREEBSD 1
    #define PLATFORM_NAME "FreeBSD"
#else
    #define PLATFORM_UNKNOWN 1
    #define PLATFORM_NAME "Unknown"
#endif

// 편의 매크로: POSIX 계열 (Linux, macOS, BSD, Android)
#if defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
    #define PLATFORM_POSIX 1
#else
    #define PLATFORM_POSIX 0
#endif

#endif // PLATFORM_DETECT_HPP

주의: _WIN32는 64비트 Windows에서도 정의됩니다. _WIN64만으로 64비트를 구분하는 것은 권장하지 않습니다. _WIN32가 있으면 Windows로 간주합니다.

CMake 플랫폼 변수

# CMake에서 플랫폼 감지
if(WIN32)
    message(STATUS "Building for Windows")
elseif(APPLE)
    message(STATUS "Building for macOS or iOS")
elseif(UNIX AND NOT APPLE)
    message(STATUS "Building for Linux")
endif()

# 아키텍처
message(STATUS "CMAKE_SYSTEM_PROCESSOR: ${CMAKE_SYSTEM_PROCESSOR}")
# x86_64, AMD64, ARM64, aarch64 등

3. std::filesystem 크로스 플랫폼 경로

C++17 std::filesystem 기본 사용

// path_example.cpp — std::filesystem으로 크로스 플랫폼 경로
#include <filesystem>
#include <iostream>
#include <string>

namespace fs = std::filesystem;

int main() {
    // 경로 결합: / 또는 \ 자동 처리
    auto config_path = fs::path("config") / "settings.json";
    std::cout << "Config: " << config_path.string() << "\n";

    // 현재 작업 디렉터리
    auto cwd = fs::current_path();
    std::cout << "CWD: " << cwd.string() << "\n";

    // 파일 존재 확인
    if (fs::exists(config_path)) {
        std::cout << "Config exists\n";
        // 파일 크기
        auto size = fs::file_size(config_path);
        std::cout << "Size: " << size << " bytes\n";
    }

    // 디렉터리 생성
    fs::create_directories("output/logs");

    // 디렉터리 순회
    for (const auto& entry : fs::directory_iterator(".")) {
        std::cout << entry.path().filename().string() << "\n";
    }

    return 0;
}

경로 유틸리티 함수

// path_utils.hpp — 크로스 플랫폼 경로 유틸
#include <filesystem>
#include <string>
#include <vector>

namespace platform {

namespace fs = std::filesystem;

inline fs::path join(const fs::path& base, const std::string& sub) {
    return base / sub;
}

inline std::string extension(const std::string& path) {
    return fs::path(path).extension().string();
}

inline std::string stem(const std::string& path) {
    return fs::path(path).stem().string();
}

inline std::string parent_path(const std::string& path) {
    return fs::path(path).parent_path().string();
}

inline bool exists(const std::string& path) {
    return fs::exists(path);
}

inline std::vector<std::string> listDirectory(const std::string& dir) {
    std::vector<std::string> result;
    for (const auto& entry : fs::directory_iterator(dir)) {
        result.push_back(entry.path().filename().string());
    }
    return result;
}

} // namespace platform

경로 구분자 주의사항

// ❌ 나쁜 예: 하드코딩된 구분자
std::string path = "config" + std::string(1, '\\') + "file.txt";  // Linux에서 문제

// ✅ 좋은 예: std::filesystem
auto path = fs::path("config") / "file.txt";
// Windows: config\file.txt
// Linux/macOS: config/file.txt

4. 동적 로딩 추상화

플랫폼별 API

플랫폼로드심볼 조회해제확장자
WindowsLoadLibraryGetProcAddressFreeLibrary.dll
Linuxdlopendlsymdlclose.so
macOSdlopendlsymdlclose.dylib, .so

완전한 동적 로딩 추상화

// dynamic_loader.hpp — 크로스 플랫폼 동적 로딩
#ifndef DYNAMIC_LOADER_HPP
#define DYNAMIC_LOADER_HPP

#include <string>
#include <functional>
#include <memory>

#if defined(_WIN32)
    #include <windows.h>
    using ModuleHandle = HMODULE;
#else
    #include <dlfcn.h>
    using ModuleHandle = void*;
#endif

class DynamicLoader {
public:
    static std::string getSharedLibExtension() {
#if defined(_WIN32)
        return ".dll";
#elif defined(__APPLE__)
        return ".dylib";
#else
        return ".so";
#endif
    }

    static std::string getSharedLibPrefix() {
#if defined(_WIN32)
        return "";
#else
        return "lib";
#endif
    }

    static std::string makeLibraryName(const std::string& base) {
        return getSharedLibPrefix() + base + getSharedLibExtension();
    }

    static ModuleHandle load(const std::string& path) {
#if defined(_WIN32)
        return LoadLibraryA(path.c_str());
#else
        return dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL);
#endif
    }

    static void* getSymbol(ModuleHandle handle, const char* name) {
#if defined(_WIN32)
        return reinterpret_cast<void*>(GetProcAddress(handle, name));
#else
        return dlsym(handle, name);
#endif
    }

    static void unload(ModuleHandle handle) {
#if defined(_WIN32)
        FreeLibrary(handle);
#else
        dlclose(handle);
#endif
    }

    static std::string getLastError() {
#if defined(_WIN32)
        DWORD err = GetLastError();
        return "Error code: " + std::to_string(err);
#else
        return dlerror() ? dlerror() : "Unknown error";
#endif
    }
};

// RAII 래퍼
class ScopedModule {
public:
    explicit ScopedModule(const std::string& path)
        : handle_(DynamicLoader::load(path)) {
        if (!handle_) {
            throw std::runtime_error("Failed to load: " + path + " - " +
                                     DynamicLoader::getLastError());
        }
    }
    ~ScopedModule() {
        if (handle_) {
            DynamicLoader::unload(handle_);
        }
    }

    template <typename Func>
    Func getFunc(const char* name) {
        void* sym = DynamicLoader::getSymbol(handle_, name);
        return reinterpret_cast<Func>(sym);
    }

    bool operator bool() const { return handle_ != nullptr; }

private:
    ModuleHandle handle_;
};

#endif // DYNAMIC_LOADER_HPP

사용 예시

// plugin_usage.cpp
#include "dynamic_loader.hpp"
#include <iostream>

int main() {
    auto lib_name = DynamicLoader::makeLibraryName("mylib");
    std::cout << "Loading: " << lib_name << "\n";

    try {
        ScopedModule mod(lib_name);
        auto add = mod.getFunc<int(int, int)>("add");
        if (add) {
            std::cout << "add(2, 3) = " << add(2, 3) << "\n";
        }
    } catch (const std::exception& e) {
        std::cerr << e.what() << "\n";
    }
    return 0;
}

5. 완전한 크로스 플랫폼 예제

5.1 최소 CMakeLists.txt (데스크톱 3대)

cmake_minimum_required(VERSION 3.16)
project(CrossPlatformApp LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)

# 플랫폼별 라이브러리
if(WIN32)
    set(PLATFORM_LIBS ws2_32)
elseif(UNIX AND NOT APPLE)
    find_package(Threads REQUIRED)
    set(PLATFORM_LIBS Threads::Threads dl)
elseif(APPLE)
    find_package(Threads REQUIRED)
    set(PLATFORM_LIBS Threads::Threads dl)
endif()

add_executable(app main.cpp)
target_link_libraries(app PRIVATE ${PLATFORM_LIBS})

# 플랫폼별 컴파일 정의
if(WIN32)
    target_compile_definitions(app PRIVATE PLATFORM_WINDOWS=1)
elseif(APPLE)
    target_compile_definitions(app PRIVATE PLATFORM_APPLE=1)
else()
    target_compile_definitions(app PRIVATE PLATFORM_LINUX=1)
endif()

5.2 전체 동작 예제: main.cpp

// main.cpp — 실행 시 플랫폼 정보 출력
#include <iostream>
#include <filesystem>
#include "platform_detect.hpp"
#include "dynamic_loader.hpp"

int main() {
    std::cout << "Platform: " << PLATFORM_NAME << "\n";
    std::cout << "CWD: " << std::filesystem::current_path().string() << "\n";

    // 경로 결합 — 모든 플랫폼에서 동작
    auto path = std::filesystem::path("config") / "settings.json";
    std::cout << "Config path: " << path.string() << "\n";

    // 동적 라이브러리 확장자
    std::cout << "Shared lib ext: " << DynamicLoader::getSharedLibExtension() << "\n";

    return 0;
}

5.3 네트워크 초기화 (Windows WSA)

// network_platform.hpp — 소켓 초기화 (Windows는 WSA 필요)
#if defined(_WIN32)
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #pragma comment(lib, "ws2_32.lib")
#else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <unistd.h>
#endif

class NetworkPlatform {
public:
    static void init() {
#if defined(_WIN32)
        WSADATA wsa;
        if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
            throw std::runtime_error("WSAStartup failed");
        }
#endif
    }

    static void shutdown() {
#if defined(_WIN32)
        WSACleanup();
#endif
    }
};

5.4 조건부 헤더 include

// ❌ 나쁜 예: 플랫폼별 헤더를 조건 없이 include
#include <windows.h>  // Linux에서 컴파일 실패

// ✅ 좋은 예: 조건부 include
#if defined(_WIN32)
    #include <windows.h>
    #include <io.h>
    #include <process.h>
    #define close _close
    #define read _read
#else
    #include <dlfcn.h>
    #include <unistd.h>
#endif

6. 툴체인: MinGW·iOS·Android

6.1 MinGW 툴체인 (Windows 크로스 컴파일)

Linux/macOS에서 Windows용 바이너리를 빌드할 때 사용합니다.

# toolchains/mingw64.toolchain.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

# MinGW 경로 (환경에 맞게 수정)
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)

set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

빌드 명령:

cmake -B build-mingw -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.toolchain.cmake
cmake --build build-mingw

6.2 iOS 툴체인

# toolchains/ios.toolchain.cmake (예시 — ios.toolchain.cmake 프로젝트 사용 권장)
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "")
set(CMAKE_OSX_SYSROOT "iphoneos" CACHE STRING "iOS SDK")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")

실제 사용: ios-cmake 같은 검증된 툴체인을 사용하는 것이 좋습니다.

cmake -B build-ios -DCMAKE_TOOLCHAIN_FILE=ios.toolchain.cmake \
  -DPLATFORM=OS64 -DDEPLOYMENT_TARGET=14.0
cmake --build build-ios

6.3 Android NDK 툴체인

# Android 빌드
cmake -B build-android \
  -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
  -DANDROID_ABI=arm64-v8a \
  -DANDROID_PLATFORM=android-21 \
  -DANDROID_STL=c++_shared
cmake --build build-android

ABI 선택:

  • arm64-v8a: 64비트 ARM (권장)
  • armeabi-v7a: 32비트 ARM
  • x86_64: 에뮬레이터
  • x86: 32비트 에뮬레이터

6.4 툴체인 선택 흐름

flowchart TB
    subgraph host[호스트]
        H1[Linux]
        H2[macOS]
        H3[Windows]
    end
    subgraph target[타겟]
        T1[Windows .exe]
        T2[iOS .app]
        T3[Android .so]
    end
    H1 -->|MinGW| T1
    H2 -->|MinGW| T1
    H2 -->|Xcode| T2
    H1 -->|NDK| T3
    H2 -->|NDK| T3
    H3 -->|NDK| T3

7. 자주 발생하는 에러와 해결법

에러 1: “fatal error: ‘unistd.h’ file not found” (Windows)

원인: unistd.h는 POSIX 전용 헤더입니다. Windows(MSVC)에는 없습니다.

해결:

// ❌ 잘못된 코드
#include <unistd.h>  // Windows에서 없음

// ✅ 올바른 코드
#if defined(_WIN32)
    #include <io.h>
    #include <process.h>
    #define close _close
    #define read _read
#else
    #include <unistd.h>
#endif

또는 std::filesystem·std::thread 등 표준 라이브러리로 대체합니다.

에러 2: “undefined reference to dlopen” (Linux)

원인: dlopenlibdl에 있습니다. 링크하지 않으면 에러가 납니다.

해결:

# CMakeLists.txt
if(UNIX AND NOT APPLE)
    target_link_libraries(app PRIVATE dl)
endif()
# 또는
if(UNIX)
    target_link_libraries(app PRIVATE dl)
endif()

에러 3: “DLL not found” 또는 “dyld: Library not loaded” (런타임)

원인: 동적 라이브러리 경로가 PATH(Windows) 또는 LD_LIBRARY_PATH(Linux)·DYLD_LIBRARY_PATH(macOS)에 없습니다.

해결:

# Linux
export LD_LIBRARY_PATH=/path/to/libs:$LD_LIBRARY_PATH
./app

# macOS
export DYLD_LIBRARY_PATH=/path/to/libs:$DYLD_LIBRARY_PATH
./app

# Windows: 실행 파일와 같은 디렉터리에 DLL을 두거나 PATH에 추가

에러 4: “symbol not found” (macOS)

원인: macOS는 기본적으로 모든 심볼이 숨겨져 있습니다. DLL을 내보내려면 __attribute__((visibility("default")))가 필요합니다.

해결:

#if defined(__APPLE__)
    #define EXPORT_API __attribute__((visibility("default")))
#elif defined(_WIN32)
    #define EXPORT_API __declspec(dllexport)
#else
    #define EXPORT_API __attribute__((visibility("default")))
#endif

extern "C" EXPORT_API void my_exported_function();

에러 5: Windows “LNK2019: unresolved external symbol”

원인: 라이브러리를 링크하지 않았거나, __declspec(dllimport)/dllexport가 누락되었습니다.

해결:

// mylib.h
#if defined(_WIN32)
    #ifdef MYLIB_EXPORTS
        #define MYLIB_API __declspec(dllexport)
    #else
        #define MYLIB_API __declspec(dllimport)
    #endif
#else
    #define MYLIB_API
#endif

MYLIB_API void my_function();
# DLL 빌드 시
add_library(mylib SHARED mylib.cpp)
target_compile_definitions(mylib PRIVATE MYLIB_EXPORTS)

에러 6: MinGW “undefined reference to WinMain”

원인: Windows GUI 앱이 아닌데 -mwindows로 링크했거나, 콘솔 앱인데 진입점이 잘못 설정되었습니다.

해결:

# 콘솔 앱인 경우 WIN32 제거
add_executable(app main.cpp)
# add_executable(app WIN32 main.cpp)  # GUI 앱만 WIN32 사용

에러 7: long 타입 크기 차이

원인: Windows 64비트에서 long은 4바이트, Linux 64비트에서는 8바이트입니다.

해결:

// ❌ 나쁜 예: long에 의존
long file_size;

// ✅ 좋은 예: 고정 크기 타입
#include <cstdint>
int64_t file_size;
uint32_t count;

에러 8: endianness (바이트 순서)

원인: ARM과 x86의 바이트 순서가 다를 수 있습니다. 바이너리 포맷으로 파일·네트워크에 쓸 때 문제가 됩니다.

해결:

#include <cstdint>

inline uint32_t swapEndian(uint32_t x) {
    return ((x >> 24) & 0xff) | ((x >> 8) & 0xff00) |
           ((x << 8) & 0xff0000) | ((x << 24) & 0xff000000);
}

#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
    #define TO_NETWORK_ORDER(x) (x)
#else
    #define TO_NETWORK_ORDER(x) swapEndian(x)
#endif

에러 9: macOS “code signing” 오류

원인: iOS·macOS 앱은 코드 서명이 필요합니다.

해결:

set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")

에러 10: Android “cannot find -lc++_shared”

원인: NDK의 C++ 공유 라이브러리 경로가 설정되지 않았습니다.

해결:

# android.toolchain.cmake 사용 시 ANDROID_STL=c++_shared
# NDK r23+ 에서는 기본값이 c++_shared

8. 베스트 프랙티스

8.1 플랫폼별 코드 최소화

// ❌ 나쁜 예: 플랫폼별 코드가 산재
void doWork() {
#ifdef _WIN32
    // Windows 전용 50줄
#else
    // Unix 전용 50줄
#endif
}

// ✅ 좋은 예: 추상화 레이어로 분리
void doWork() {
    platform::init();
    platform::execute();
    platform::cleanup();
}

8.2 std::filesystem 사용

// ❌ 나쁜 예
std::string path = "config" + std::string(1, '/') + "file.txt";

// ✅ 좋은 예
auto path = std::filesystem::path("config") / "file.txt";

8.3 extern “C”로 ABI 안정화

// 플러그인·DLL 경계는 C ABI 사용
extern "C" {
    EXPORT_API void plugin_init();
    EXPORT_API void plugin_shutdown();
}

8.4 CI에서 멀티 플랫폼 빌드

# .github/workflows/build.yml
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release
      - name: Build
        run: cmake --build build

8.5 툴체인 파일 분리

project/
├── toolchains/
│   ├── linux.toolchain.cmake
│   ├── mingw64.toolchain.cmake
│   ├── ios.toolchain.cmake
│   └── android.toolchain.cmake
├── CMakeLists.txt
└── src/

8.6 정수 타입

// 플랫폼마다 long 크기가 다를 수 있음
// ✅ 고정 크기 타입 사용
#include <cstdint>
int64_t value;   // 항상 8바이트
uint32_t count;  // 항상 4바이트

9. 프로덕션 패턴

9.1 통합 빌드 스크립트

#!/bin/bash
# build-all.sh — 모든 플랫폼 빌드
set -e

BUILD_DIR=build
PLATFORMS="linux windows macos"

for p in $PLATFORMS; do
    echo "Building for $p..."
    case $p in
        linux)
            cmake -B $BUILD_DIR/linux -DCMAKE_BUILD_TYPE=Release
            cmake --build $BUILD_DIR/linux
            ;;
        windows)
            cmake -B $BUILD_DIR/windows -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.toolchain.cmake
            cmake --build $BUILD_DIR/windows
            ;;
        macos)
            cmake -B $BUILD_DIR/macos -DCMAKE_BUILD_TYPE=Release
            cmake --build $BUILD_DIR/macos
            ;;
    esac
done

9.2 버전·플랫폼 정보 주입

# CMakeLists.txt
if(EXISTS "${CMAKE_SOURCE_DIR}/.git")
    execute_process(
        COMMAND git rev-parse --short HEAD
        WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
        OUTPUT_VARIABLE GIT_HASH
        OUTPUT_STRIP_TRAILING_WHITESPACE
    )
else()
    set(GIT_HASH "unknown")
endif()

target_compile_definitions(app PRIVATE
    GIT_HASH="${GIT_HASH}"
    BUILD_PLATFORM="${CMAKE_SYSTEM_NAME}"
)

9.3 CPack 패키징

include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")

if(WIN32)
    set(CPACK_GENERATOR "NSIS;ZIP")
elseif(APPLE)
    set(CPACK_GENERATOR "DragNDrop;TGZ")
else()
    set(CPACK_GENERATOR "DEB;RPM;TGZ")
endif()

include(CPack)

9.4 환경 변수 기반 툴체인 선택

#!/bin/bash
TARGET=${1:-native}

case $TARGET in
    native)
        cmake -B build
        ;;
    windows)
        cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.cmake
        ;;
    android)
        cmake -B build -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
              -DANDROID_ABI=arm64-v8a
        ;;
    *)
        echo "Unknown target: $TARGET"
        exit 1
        ;;
esac

cmake --build build

9.5 플랫폼별 최적화 플래그

if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64")
        target_compile_options(app PRIVATE -march=native)
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
        target_compile_options(app PRIVATE -mcpu=native)
    endif()
elseif(MSVC)
    target_compile_options(app PRIVATE /arch:AVX2)
endif()

10. 구현 체크리스트

  • std::filesystem으로 경로 처리
  • 플랫폼별 매크로로 #ifdef 분기
  • target_link_libraries에 플랫폼별 라이브러리 추가 (dl, ws2_32, Threads)
  • 동적 로딩 시 확장자(.dll/.so/.dylib) 분기
  • extern "C"로 ABI 안정성 확보 (플러그인·DLL)
  • iOS·Android 툴체인 파일 준비
  • CI에서 Windows·Linux·macOS 빌드 검증
  • unistd.h 등 POSIX 전용 헤더 조건부 include
  • int64_t, uint32_t 등 고정 크기 타입 사용
  • MinGW vs MSVC ABI 혼용 금지

정리

항목설명
플랫폼 감지_WIN32, __linux__, __APPLE__, __ANDROID__, TARGET_OS_IPHONE
경로std::filesystem::path로 크로스 플랫폼
동적 로딩LoadLibrary/dlopen 추상화, 확장자별 분기
CMakeWIN32, APPLE, UNIX 변수
툴체인MinGW·iOS·Android -DCMAKE_TOOLCHAIN_FILE
에러unistd.h·dlopen·DLL 경로·visibility·long 크기

핵심 원칙:

  1. 플랫폼별 코드 최소화: 추상화 레이어로 분리
  2. 표준 라이브러리 우선: std::filesystem, std::thread
  3. CMake 툴체인 활용: MinGW·iOS·Android
  4. CI에서 멀티 플랫폼 빌드: 매트릭스 빌드로 검증

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. 크로스 플랫폼 라이브러리, 멀티 OS 지원 앱, 모바일 게임, 플러그인 시스템 등에 활용합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. MinGW와 MSVC 중 어떤 것을 써야 하나요?

A. Windows 전용이라면 MSVC가 편합니다. Linux에서 Windows 크로스 컴파일이 필요하면 MinGW를 사용합니다. MinGW와 MSVC로 빌드한 DLL을 섞어 쓰면 ABI 불일치로 크래시가 발생하므로, 플러그인 경계는 extern "C"로 통일합니다.

Q. 프로덕션에서 주의할 점은?

A. (1) CI에서 모든 지원 플랫폼을 매트릭스로 빌드해 검증하고, (2) 플랫폼별 바이너리를 별도 아티팩트로 배포하며, (3) ABI 호환성이 필요한 플러그인·DLL은 extern "C"로 경계를 만듭니다.

한 줄 요약: Windows·Linux·macOS·모바일를 마스터할 수 있습니다.


관련 글

  • C++ 크로스 플랫폼 빌드 완벽 가이드 | CMake 툴체인·CPack·ABI 안정성·프로덕션 패턴
  • C++ 크로스 플랫폼 테스트 완벽 가이드 | CI 매트릭스·Docker·엔디안·프로덕션 패턴 [실전]
  • C++ ABI 호환성 완벽 가이드 | PIMPL·C 인터페이스·버전 관리·프로덕션 패턴 [#55-4]
  • CMake 입문 | 수십 개 파일 컴파일할 때 필요한 빌드 자동화 (CMakeLists.txt 기초)
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3