[2026] C++ 크로스 플랫폼 빌드 완벽 가이드 | CMake 툴체인·CPack·ABI 안정성·프로덕션 패턴

[2026] C++ 크로스 플랫폼 빌드 완벽 가이드 | CMake 툴체인·CPack·ABI 안정성·프로덕션 패턴

이 글의 핵심

C++ 크로스 플랫폼 빌드 입니다. CMake 툴체인, CPack 패키징, ABI 안정성, 프로덕션 배포 전략을 실전 예제로 다룹니다. 크로스 플랫폼 C++ 프로젝트에서는 플랫폼별 경로·라이브러리·컴파일러·ABI가 달라 빌드 실패, 런타임 크래시, 패키징 불일치가 빈번합니다. 비유하면 각 나라마다 전압·플러그·규격이 다른 것처럼,.

들어가며: “팀원마다 OS가 다른데 빌드가 매번 깨져요"

"Windows에서 빌드한 DLL을 Linux 서버에서 로드하려다 크래시했어요”

크로스 플랫폼 C++ 프로젝트에서는 플랫폼별 경로·라이브러리·컴파일러·ABI가 달라 빌드 실패, 런타임 크래시, 패키징 불일치가 빈번합니다. 비유하면 “각 나라마다 전압·플러그·규격이 다른 것”처럼, OS별로 빌드·실행·배포 환경이 다르기 때문에 툴체인·조건부 컴파일·ABI 안정화가 필수입니다. 문제의 핵심:

  • Windows: \ 경로, LoadLibrary, ws2_32.dll, MSVC/MinGW ABI 불일치
  • Linux: / 경로, dlopen, pthread, GCC/Clang, 심볼 버전 관리
  • macOS: dlopen, pthread, Clang, 코드 서명·Xcode 규칙
  • 모바일: iOS/Android 각각 별도 툴체인·SDK·ABI 이 글에서 다루는 것:
  • 문제 시나리오: 실무에서 겪는 크로스 플랫폼 빌드 고통 8가지
  • 완전한 CMake 툴체인 파일: Linux·Windows·MinGW·iOS·Android
  • CPack 패키징: DEB·RPM·NSIS·DMG·ZIP
  • ABI 안정성: PIMPL·extern C·심볼 버전 관리
  • 자주 발생하는 에러와 해결법
  • 모범 사례프로덕션 패턴 요구 환경: C++17 이상, CMake 3.16+

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

목차

  1. 문제 시나리오: 크로스 플랫폼 빌드가 깨지는 순간
  2. 기본 개념: 플랫폼 감지·툴체인 흐름
  3. 완전한 CMake 툴체인 파일
  4. CPack 크로스 플랫폼 패키징
  5. ABI 안정성: PIMPL·extern C·심볼 버전
  6. 자주 발생하는 에러와 해결법
  7. 모범 사례
  8. 프로덕션 패턴
  9. 정리

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

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

문제: Windows에서 path/to/file.txt 같은 경로로 파일을 열면 std::ifstream이 실패합니다. Windows는 \를 구분자로 쓰지만, Linux는 /를 씁니다. 하드코딩된 경로로는 한 플랫폼에서만 동작합니다. 해결: std::filesystem::path를 사용하면 OS가 자동으로 올바른 구분자를 사용합니다.

시나리오 2: Windows에서만 “undefined reference to pthread”

문제: Linux에서 -lpthread로 링크한 코드가 Windows에서 빌드됩니다. Windows에는 pthread가 없고, MSVC는 스레드를 기본 지원합니다. CMake find_package(Threads)로 플랫폼별 스레드 라이브러리를 자동 링크합니다.

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

문제: 플러그인을 로드할 때 plugin.so로만 찾습니다. Windows에서는 plugin.dll, macOS에서는 plugin.dylib이 필요합니다. 해결: CMAKE_SHARED_LIBRARY_SUFFIX 또는 플랫폼별 매크로로 확장자를 분기합니다.

시나리오 4: 모바일 크로스 컴파일 실패

문제: 데스크톱용 CMake로 iOS·Android 앱을 빌드하려 합니다. arm64·armv7 아키텍처, iOS SDK 경로, Android NDK 경로가 설정되지 않아 빌드가 실패합니다. 해결: CMake 툴체인 파일로 iOS/Android 전용 컴파일러·SDK를 지정합니다.

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

문제: Windows에서 MSVC로 빌드한 DLL을 MinGW로 빌드한 앱에서 로드합니다. C++ ABI·표준 라이브러리(libstdc++ vs MSVCRT)가 달라 런타임 크래시가 발생합니다. 해결: C ABI로 경계를 만들거나, 같은 툴체인으로 빌드합니다. 플러그인·DLL은 extern "C"로 내보냅니다.

시나리오 6: 패키징 시 의존성 누락

문제: CPack으로 DEB를 만들었는데, 설치 후 실행 시 “libfoo.so.1: cannot open shared object file” 에러가 납니다. 런타임 의존성이 패키지에 포함되지 않았습니다. 해결: CPACK_INSTALL_CMAKE_PROJECTS, CPACK_DEBIAN_PACKAGE_DEPENDS 등으로 의존성을 명시하고, install(RUNTIME_DEPENDENCY_SET)으로 동적 라이브러리를 수집합니다.

시나리오 7: Linux에서 심볼 버전 충돌

문제: libstdc++를 업그레이드한 후, 구버전으로 빌드한 플러그인이 “version `GLIBCXX_3.4.30’ not found”로 로드 실패합니다. 해결: 플러그인에 심볼 버전 스크립트를 적용해 필요한 심볼만 내보내고, 배포 시 최소 glibc/libstdc++ 버전을 명시합니다.

시나리오 8: CI에서 플랫폼별 빌드 매트릭스 실패

문제: 로컬 macOS에서는 빌드가 되는데, GitHub Actions의 windows-latest에서만 실패합니다. MSVC 경로·vcpkg triplet·코드 페이지 설정이 다릅니다. 해결: 툴체인 파일을 표준화하고, CI 스크립트에서 플랫폼별로 동일한 CMake 옵션을 사용합니다.

2. 기본 개념: 플랫폼 감지·툴체인 흐름

플랫폼 감지 매크로

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// platform_detect.hpp — 플랫폼별 매크로
#if defined(_WIN32) || defined(_WIN64)
    #define PLATFORM_WINDOWS 1
#elif defined(__APPLE__)
    #include <TargetConditionals.h>
    #if TARGET_OS_IPHONE
        #define PLATFORM_IOS 1
    #else
        #define PLATFORM_MACOS 1
    #endif
#elif defined(__linux__)
    #if defined(__ANDROID__)
        #define PLATFORM_ANDROID 1
    #else
        #define PLATFORM_LINUX 1
    #endif
#else
    #define PLATFORM_UNKNOWN 1
#endif

CMake 플랫폼 변수

아래 코드는 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 등

크로스 플랫폼 빌드 흐름

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

flowchart TB
    subgraph source[소스 코드]
        S1[공통 C++ 코드]
        S2[플랫폼별 #ifdef]
        S3[추상화 레이어]
    end
    subgraph cmake[CMake]
        C1[CMakeLists.txt]
        C2[툴체인 파일]
        C3[플랫폼별 옵션]
    end
    subgraph targets[타겟 플랫폼]
        T1[Windows .exe/.dll]
        T2[Linux .so]
        T3[macOS .dylib]
        T4[iOS/Android]
    end
    S1 --> C1
    S2 --> C1
    S3 --> C1
    C1 --> C2
    C1 --> C3
    C2 --> T1
    C2 --> T2
    C2 --> T3
    C2 --> T4

3. 완전한 CMake 툴체인 파일

3.1 프로젝트 구조

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

3.2 Linux x64 툴체인 (크로스 컴파일용)

다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# toolchains/linux-x64.cmake
# 사용: cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/linux-x64.cmake
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# 크로스 컴파일러 지정 (선택)
# set(CMAKE_C_COMPILER /usr/bin/x86_64-linux-gnu-gcc)
# set(CMAKE_CXX_COMPILER /usr/bin/x86_64-linux-gnu-g++)
# sysroot (크로스 컴파일 시)
# set(CMAKE_SYSROOT /path/to/sysroot)
# 빌드 머신 도구는 호스트 것 사용
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

3.3 MinGW64 툴체인 (Linux/macOS에서 Windows 빌드)

다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# toolchains/mingw64.cmake
# 사용: cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchains/mingw64.cmake
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
# MinGW 경로 (환경에 맞게 수정)
set(MINGW_PREFIX /usr/local/x86_64-w64-mingw32
    CACHE PATH "MinGW-w64 installation prefix")
set(CMAKE_C_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-gcc)
set(CMAKE_CXX_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-g++)
set(CMAKE_RC_COMPILER ${MINGW_PREFIX}/bin/x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH ${MINGW_PREFIX})
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# 정적 링크 옵션 (선택)
# set(CMAKE_EXE_LINKER_FLAGS "-static-libgcc -static-libstdc++")

3.4 iOS 툴체인

다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# toolchains/ios.toolchain.cmake
# 사용: cmake -B build-ios -DCMAKE_TOOLCHAIN_FILE=toolchains/ios.toolchain.cmake
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_SYSROOT iphoneos CACHE STRING "iOS SDK")
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "iOS architectures")
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING "Minimum iOS version")
# 코드 서명 (개발 빌드)
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" CACHE STRING "")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" CACHE STRING "")
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" CACHE STRING "")
# 빌드 타입
if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release CACHE STRING "")
endif()

3.5 Android NDK 툴체인

아래 코드는 cmake를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# toolchains/android-arm64.cmake
# 사용: cmake -B build-android \
#   -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
#   -DANDROID_ABI=arm64-v8a \
#   -DANDROID_PLATFORM=android-21
# (Android NDK는 자체 툴체인 제공 — android.toolchain.cmake 사용)
# 커스텀 옵션 예시
set(ANDROID_ABI arm64-v8a CACHE STRING "")
set(ANDROID_PLATFORM android-21 CACHE STRING "")
set(ANDROID_STL c++_shared CACHE STRING "")

3.6 메인 CMakeLists.txt — 툴체인 통합

다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# CMakeLists.txt
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)
    find_library(DL_LIBRARY dl)
    set(PLATFORM_LIBS Threads::Threads ${DL_LIBRARY})
elseif(APPLE)
    find_package(Threads REQUIRED)
    set(PLATFORM_LIBS Threads::Threads)
endif()
add_executable(app src/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()
# 공유 라이브러리 (플러그인)
add_library(plugin SHARED src/plugin.cpp)
target_link_libraries(plugin PRIVATE ${PLATFORM_LIBS})
set_target_properties(plugin PROPERTIES
    POSITION_INDEPENDENT_CODE ON
    WINDOWS_EXPORT_ALL_SYMBOLS OFF
)
if(NOT WIN32)
    target_compile_definitions(plugin PRIVATE PLUGIN_EXPORT=__attribute__\(\(visibility\(\"default\"\)\)\))
endif()

4. CPack 크로스 플랫폼 패키징

4.1 CPack 기본 설정

다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# CMakeLists.txt 하단에 추가
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VENDOR "MyCompany")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Cross-platform C++ Application")
set(CPACK_RESOURCE_FILE_README "${CMAKE_SOURCE_DIR}/README.md")
# 설치 규칙
install(TARGETS app RUNTIME DESTINATION bin)
install(TARGETS plugin LIBRARY DESTINATION lib RUNTIME DESTINATION bin)
# 플랫폼별 제너레이터
if(WIN32)
    set(CPACK_GENERATOR "NSIS;ZIP")
    set(CPACK_NSIS_MODIFY_PATH ON)
    set(CPACK_NSIS_INSTALLER_MUI_ICON "${CMAKE_SOURCE_DIR}/icon.ico")
elseif(APPLE)
    set(CPACK_GENERATOR "DragNDrop;TGZ")
    set(CPACK_DMG_DS_STORE_SET_OWNER "${CMAKE_INSTALL_PREFIX}")
else()
    set(CPACK_GENERATOR "DEB;RPM;TGZ")
    set(CPACK_DEBIAN_PACKAGE_MAINTAINER "maintainer@example.com")
    set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.27)")
    set(CPACK_RPM_PACKAGE_LICENSE "MIT")
endif()
include(CPack)

4.2 DEB 패키지 상세 설정

아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# DEB 전용 옵션
set(CPACK_DEBIAN_PACKAGE_NAME "myapp")
set(CPACK_DEBIAN_PACKAGE_VERSION "1.0.0")
set(CPACK_DEBIAN_PACKAGE_ARCHITECTURE "amd64")
set(CPACK_DEBIAN_PACKAGE_DEPENDS "libc6 (>= 2.27), libstdc++6 (>= 9)")
set(CPACK_DEBIAN_PACKAGE_SECTION "utils")
set(CPACK_DEBIAN_PACKAGE_PRIORITY "optional")

4.3 NSIS (Windows 인스톨러) 설정

아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

set(CPACK_NSIS_PACKAGE_NAME "MyApp")
set(CPACK_NSIS_DISPLAY_NAME "My Application")
set(CPACK_NSIS_HELP_LINK "https://example.com/support")
set(CPACK_NSIS_URL_INFO_ABOUT "https://example.com")
set(CPACK_NSIS_CONTACT "contact@example.com")
set(CPACK_NSIS_MODIFY_PATH ON)

4.4 런타임 의존성 수집 (CMake 3.21+)

다음은 cmake를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# 동적 라이브러리 의존성 자동 수집
include(GNUInstallDirs)
install(TARGETS app plugin
    RUNTIME
        COMPONENT runtime
        DESTINATION ${CMAKE_INSTALL_BINDIR}
    LIBRARY
        COMPONENT runtime
        DESTINATION ${CMAKE_INSTALL_LIBDIR}
)
install(RUNTIME_DEPENDENCY_SET app_deps
    TARGETS app
    PRE_INCLUDE_REGEXES ".*"
    PRE_EXCLUDE_REGEXES ".*system.*"
    POST_INCLUDE_REGEXES ".*"
    POST_EXCLUDE_REGEXES ""
    DIRECTORIES ${CMAKE_SOURCE_DIR}/lib
)

4.5 CPack 빌드 명령

다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 빌드 후 패키징
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && cpack -G DEB   # 또는 NSIS, ZIP, TGZ 등

5. ABI 안정성: PIMPL·extern C·심볼 버전

5.1 PIMPL로 ABI 고정

PIMPL(Pointer to Implementation)은 공개 클래스가 구현체 포인터만 갖고, 구현체 정의는 .cpp에만 두어 공개 클래스 크기를 고정합니다. 내부 구현 변경 시에도 ABI가 유지됩니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// widget.h — 공개 헤더 (ABI 고정)
#pragma once
#include <memory>
#include <string>
class WidgetImpl;
class Widget {
public:
    Widget();
    ~Widget();
    Widget(const Widget&);
    Widget& operator=(const Widget&);
    Widget(Widget&&) noexcept = default;
    Widget& operator=(Widget&&) noexcept = default;
    void setTitle(const std::string& title);
    [[nodiscard]] std::string getTitle() const;
private:
    std::unique_ptr<WidgetImpl> pImpl_;  // 크기 고정: 8바이트
};

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// widget.cpp — 구현 (내부 변경 가능)
#include "widget.h"
#include <unordered_map>
class WidgetImpl {
public:
    std::string title;
    std::unordered_map<std::string, size_t> cache;  // v2에서 추가해도 ABI 유지
};
Widget::Widget() : pImpl_(std::make_unique<WidgetImpl>()) {}
Widget::~Widget() = default;
Widget::Widget(const Widget& other)
    : pImpl_(std::make_unique<WidgetImpl>(*other.pImpl_)) {}
Widget& Widget::operator=(const Widget& other) {
    if (this != &other) *pImpl_ = *other.pImpl_;
    return *this;
}
void Widget::setTitle(const std::string& title) { pImpl_->title = title; }
std::string Widget::getTitle() const { return pImpl_->title; }

5.2 extern “C” 플러그인 API

C++ name mangling은 컴파일러마다 다릅니다. extern “C”로 내보내면 심볼 이름이 고정되어, 다른 컴파일러로 빌드한 바이너리와도 링크할 수 있습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// plugin_api.h — C 인터페이스 (ABI 최대 안정)
#pragma once
#include <cstdint>
#include <cstddef>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct PluginHandle* PluginHandlePtr;
PluginHandlePtr plugin_create(const char* config);
void plugin_destroy(PluginHandlePtr handle);
int plugin_process(PluginHandlePtr handle, const void* input, size_t input_size,
                   void* output, size_t output_size);
#ifdef __cplusplus
}
#endif

다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// plugin_impl.cpp — C++ 래퍼가 C API 구현
#include "plugin_api.h"
#include <string>
#include <cstring>
struct PluginImpl {
    std::string config;
};
extern "C" {
#if defined(_WIN32)
    #define PLUGIN_EXPORT __declspec(dllexport)
#else
    #define PLUGIN_EXPORT __attribute__((visibility("default")))
#endif
PLUGIN_EXPORT PluginHandlePtr plugin_create(const char* config) {
    auto* p = new PluginImpl;
    if (config) p->config = config;
    return p;
}
PLUGIN_EXPORT void plugin_destroy(PluginHandlePtr handle) {
    delete static_cast<PluginImpl*>(handle);
}
PLUGIN_EXPORT int plugin_process(PluginHandlePtr handle,
    const void* input, size_t input_size, void* output, size_t output_size) {
    (void)handle;
    (void)input;
    (void)input_size;
    (void)output;
    (void)output_size;
    return 0;
}
}

5.3 Linux 심볼 버전 관리

플러그인·라이브러리에서 필요한 심볼만 내보내고, 버전을 부여해 ABI 호환성을 관리합니다. 아래 코드는 text를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# plugin.ver — 심볼 버전 스크립트
PLUGIN_1.0 {
  global:
    plugin_create;
    plugin_destroy;
    plugin_process;
  local: *;
};
PLUGIN_1.1 {
  global:
    plugin_init;
} PLUGIN_1.0;

아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# CMakeLists.txt — Linux에서 심볼 버전 적용
// 실행 예제
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    target_link_options(plugin PRIVATE
        "LINKER:--version-script=${CMAKE_CURRENT_SOURCE_DIR}/plugin.ver"
    )
endif()
# 직접 링크 시
g++ -shared -Wl,--version-script=plugin.ver -o libplugin.so plugin.cpp

5.4 ABI 안정성 비교

기법ABI 안정성사용 편의성적용 시점
PIMPL높음중간공개 C++ 클래스
extern “C”최고낮음플러그인·DLL 경계
심볼 버전중간낮음Linux .so 버전 관리

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

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

원인: unistd.h는 POSIX 전용 헤더입니다. Windows(MSVC)에는 없습니다. 해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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

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

원인: dlopenlibdl에 있습니다. 링크하지 않으면 에러가 납니다. 해결: 다음은 간단한 cmake 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# CMakeLists.txt
if(UNIX AND NOT APPLE)
    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)에 없습니다. 해결: 아래 코드는 bash를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# 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는 기본적으로 모든 심볼이 숨겨져 있습니다. 내보내려면 __attribute__((visibility("default")))가 필요합니다. 해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

#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: “LNK2019: unresolved external symbol” (Windows)

원인: __declspec(dllimport)/dllexport가 누락되었습니다. 해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 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();
add_library(mylib SHARED mylib.cpp)
target_compile_definitions(mylib PRIVATE MYLIB_EXPORTS)

에러 6: MinGW에서 “undefined reference to WinMain”

원인: 콘솔 앱인데 add_executable(app WIN32 ...)로 GUI 앱으로 빌드했습니다. 해결:

# 콘솔 앱
add_executable(app main.cpp)
# WIN32 제거

에러 7: “version `GLIBCXX_3.4.30’ not found”

원인: 구버전 libstdc++로 빌드한 바이너리를 새 환경에서 실행하려 할 때, 또는 그 반대입니다. 해결: 동일한 툴체인으로 빌드하거나, -static-libstdc++로 정적 링크합니다. 배포 시 최소 glibc/libstdc++ 버전을 명시합니다.

에러 8: CPack DEB 빌드 시 “dpkg-shlibdeps: error”

원인: dpkg-shlibdeps가 패키지된 라이브러리의 의존성을 분석할 수 없습니다. 경로가 잘못되었거나 라이브러리가 누락되었습니다. 해결: 다음은 간단한 cmake 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

# CPACK_DEBIAN_PACKAGE_SHLIBDEPS를 OFF로 (권장하지 않음)
# 또는 install(RUNTIME_DEPENDENCY_SET)으로 의존성 수집
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
set(CPACK_DEBIAN_PACKAGE_GENERATE_SHLIBS ON)

에러 9: endianness·정렬 문제

원인: ARM과 x86의 바이트 순서가 다를 수 있습니다. 해결: 아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#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

7. 모범 사례

7.1 플랫폼별 코드 최소화

다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 나쁜 예: 플랫폼별 코드가 산재
void doWork() {
#ifdef _WIN32
    // Windows 전용 50줄
#else
    // Unix 전용 50줄
#endif
}
// ✅ 좋은 예: 추상화 레이어로 분리
void doWork() {
    platform::init();
    platform::execute();
    platform::cleanup();
}

7.2 std::filesystem 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

7.3 고정 크기 정수 타입

다음은 간단한 cpp 코드 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

7.4 CI에서 멀티 플랫폼 빌드

다음은 yaml를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

# .github/workflows/build.yml
jobs:
  build:
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-cpp@v1
        with:
          compiler: ${{ matrix.os == 'windows-latest' && 'msvc' || 'gcc' }}
      - name: Configure
        run: cmake -B build -DCMAKE_BUILD_TYPE=Release
      - name: Build
        run: cmake --build build
      - name: Package
        run: cd build && cpack -G ${{ matrix.os == 'ubuntu-latest' && 'DEB' || (matrix.os == 'windows-latest' && 'NSIS' || 'TGZ') }}

7.5 툴체인 파일 분리

아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

7.6 ABI 경계에서 C 타입 사용

아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ❌ API 경계에 std::string
extern "C" void process(std::string input);
// ✅ C 타입 사용
extern "C" void process(const char* input, size_t len);

8. 프로덕션 패턴

8.1 통합 빌드 스크립트

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#!/bin/bash
# build-all.sh — 모든 플랫폼 빌드
set -e
BUILD_DIR=build
for p in linux windows macos; 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.cmake
            cmake --build $BUILD_DIR/windows
            ;;
        macos)
            cmake -B $BUILD_DIR/macos -DCMAKE_BUILD_TYPE=Release
            cmake --build $BUILD_DIR/macos
            ;;
    esac
done

8.2 버전·플랫폼 정보 주입

다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

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}"
)

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

다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

#!/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

8.4 플랫폼별 최적화 플래그

아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

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()

8.5 빌드 검증 시퀀스

다음은 mermaid를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

sequenceDiagram
    participant Dev as 개발자
    participant CMake as CMake
    participant CC as 컴파일러
    participant CI as CI
    Dev->>CMake: cmake -B build
    CMake->>CMake: 플랫폼 감지
    CMake->>CMake: 툴체인 설정
    CMake-->>Dev: Makefile/솔루션 생성
    Dev->>CC: cmake --build build
    CC-->>Dev: 바이너리
    Dev->>CI: push
    CI->>CMake: 매트릭스 빌드 (Win/Linux/macOS)
    CMake->>CC: 각 플랫폼 빌드
    CI-->>Dev: 빌드 결과

9. 정리

항목설명
플랫폼 감지_WIN32, __linux__, __APPLE__ 등 매크로
툴체인-DCMAKE_TOOLCHAIN_FILE로 크로스 컴파일
CPackDEB·RPM·NSIS·ZIP·TGZ 플랫폼별 패키징
ABI 안정성PIMPL·extern C·심볼 버전
에러unistd.h·dlopen·DLL 경로·visibility
핵심 원칙:
  1. 플랫폼별 코드 최소화: 추상화 레이어로 분리
  2. 표준 라이브러리 우선: std::filesystem, std::thread
  3. CMake 툴체인 활용: iOS·Android·MinGW 등
  4. ABI 경계: extern C 또는 PIMPL
  5. CI에서 멀티 플랫폼 빌드: 매트릭스 빌드로 검증

구현 체크리스트

  • std::filesystem으로 경로 처리
  • 플랫폼별 매크로로 #ifdef 분기
  • target_link_libraries에 플랫폼별 라이브러리 추가
  • 동적 로딩 시 확장자(.dll/.so/.dylib) 분기
  • extern "C"로 ABI 안정성 확보 (플러그인·DLL)
  • iOS·Android 툴체인 파일 준비
  • CPack으로 DEB·NSIS·TGZ 패키징
  • CI에서 Windows·Linux·macOS 빌드 검증
  • Linux 심볼 버전 스크립트 (선택)

자주 묻는 질문 (FAQ)

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

A. 크로스 플랫폼 라이브러리 배포, 멀티 OS 지원 앱, 플러그인 시스템, CI/CD 파이프라인 구축 시 활용합니다. 본문의 툴체인·CPack·ABI 예제를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. CMake 기초(#04), CMake 고급(#17-1), 크로스 플랫폼(#55-4), ABI 안정성(#55-8)을 먼저 읽으면 이해가 쉽습니다.

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

A. CI에서 모든 지원 플랫폼을 매트릭스로 빌드 검증하고, ABI 경계는 extern C 또는 PIMPL로 고정하며, CPack으로 일관된 패키징을 적용합니다.

Q. vcpkg·Conan과 크로스 플랫폼 빌드를 함께 쓰려면?

A. vcpkg는 --triplet x64-windows 등으로 타겟을 지정합니다. Conan은 -s os=Linux -s arch=x86_64처럼 프로필로 지정합니다. CMake 툴체인과 triplet·프로필이 일치해야 합니다.

참고 자료


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3