[2026] C++ 크로스 플랫폼 빌드 | Windows·Linux·macOS·모바일 완벽 가이드 [#55-4]
이 글의 핵심
C++ 멀티 플랫폼 빌드 실전: CMake 툴체인, 조건부 컴파일, 플랫폼별 최적화, 모바일 빌드. Windows에서 빌드한 게 Linux에서 안 돌아가요 문제부터 프로덕션 패턴까지.
들어가며: Windows에서 빌드한 게 Linux에서 안 돌아가요
”팀원 A는 Windows, B는 macOS, C는 Linux인데 빌드가 매번 깨져요”
크로스 플랫폼 C++ 프로젝트를 운영하면 플랫폼마다 다른 경로 구분자·라이브러리·API 때문에 빌드가 실패하거나, 한 OS에서만 동작하는 코드가 섞여 들어갑니다. 비유하면 “한 나라에서 쓰는 전기 플러그를 다른 나라로 가져가면 어댑터가 필요해요”처럼, OS별로 빌드·실행 환경이 다르기 때문에 툴체인·조건부 컴파일·플랫폼 추상화가 필요합니다. 문제의 핵심:
- Windows:
\경로,LoadLibrary,ws2_32.dll, MSVC - Linux:
/경로,dlopen,pthread, GCC/Clang - macOS:
/경로,dlopen,pthread, Clang, Xcode 특별 규칙 - 모바일: iOS/Android 각각 별도 툴체인·SDK 이 글에서 다루는 것:
- 문제 시나리오: 실무에서 겪는 크로스 플랫폼 빌드 고통
- CMake 툴체인: 플랫폼별 컴파일러·SDK 설정
- 조건부 컴파일:
#ifdef로 플랫폼별 코드 분리 - 완전한 크로스 플랫폼 예제: Windows·Linux·macOS·모바일 빌드
- 자주 발생하는 에러와 해결법
- 모범 사례와 프로덕션 패턴 요구 환경: C++17 이상, CMake 3.16+ 이 글을 읽으면:
- 핵심 개념을 이해할 수 있습니다.
- 실전에서 활용할 수 있습니다.
- 성능을 최적화할 수 있습니다.
실무 적용 경험: 이 글은 대규모 C++ 프로젝트에서 실제로 겪은 문제와 해결 과정을 바탕으로 작성되었습니다. 책이나 문서에서 다루지 않는 실전 함정과 디버깅 팁을 포함합니다.
목차
1. 문제 시나리오: 크로스 플랫폼 빌드가 깨지는 순간
시나리오 1: 경로 구분자로 인한 파일 열기 실패
문제: Windows에서 path/to/file.txt 같은 경로로 파일을 열면 std::ifstream이 실패합니다. Windows는 \를 구분자로 쓰지만, Linux는 /를 씁니다. 하드코딩된 경로로는 한 플랫폼에서만 동작합니다.
해결: std::filesystem::path를 사용하면 OS가 자동으로 올바른 구분자를 사용합니다. /를 사용해도 Windows는 내부적으로 처리합니다.
시나리오 2: Windows에서만 “undefined reference to pthread”
문제: Linux에서 -lpthread로 링크한 코드가 Windows에서 빌드됩니다. Windows에는 pthread가 없고, MSVC는 pthread를 기본으로 지원하지 않습니다. find_package(Threads)를 쓰면 CMake가 플랫폼별로 올바른 스레드 라이브러리를 링크합니다.
해결: CMake에서 target_link_libraries(myapp PRIVATE Threads::Threads)로 플랫폼별 스레드 라이브러리를 자동 링크합니다.
시나리오 3: 동적 라이브러리 확장자 불일치
문제: 플러그인을 로드할 때 plugin.so로만 찾습니다. Windows에서는 plugin.dll, macOS에서는 plugin.dylib이 필요합니다. 확장자를 하드코딩하면 다른 OS에서 로드 실패합니다.
해결: CMAKE_SHARED_LIBRARY_SUFFIX를 사용하거나, 플랫폼별 매크로로 확장자를 분기합니다.
시나리오 4: 모바일 크로스 컴파일 실패
문제: 데스크톱용 CMake로 iOS·Android 앱을 빌드하려 합니다. arm64·armv7 아키텍처, iOS SDK 경로, Android NDK 경로가 설정되지 않아 빌드가 실패합니다.
해결: CMake 툴체인 파일(-DCMAKE_TOOLCHAIN_FILE=<path>)로 iOS/Android 전용 컴파일러·SDK를 지정합니다.
시나리오 5: 플랫폼별 API 혼용
문제: #ifdef _WIN32 블록 안에 CreateFile을 쓰고, Linux 블록에 open을 쓰는데, 공통 헤더에서 한쪽만 include되어 컴파일 에러가 납니다.
해결: 플랫폼 추상화 레이어를 두고, Platform::openFile() 같은 공통 인터페이스로 감싸서 플랫폼별 구현을 분리합니다.
시나리오 6: ABI/호환성 문제
문제: Windows에서 MSVC로 빌드한 DLL을 MinGW로 빌드한 앱에서 로드하려 합니다. C++ ABI·표준 라이브러리(libstdc++ vs MSVCRT)가 달라 크래시가 발생합니다.
해결: C ABI로 경계를 만들거나, 같은 툴체인으로 빌드합니다. 플러그인·DLL은 extern "C"로 내보내는 것이 안전합니다.
2. 기본 개념
플랫폼 감지 매크로
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 플랫폼별 매크로 — 컴파일러마다 정의됨
// Windows: _WIN32 (32비트·64비트 공통), _WIN64 (64비트만)
// Linux: __linux__
// macOS: __APPLE__ (macOS + iOS), __MACH__ (Mach 커널)
// Android: __ANDROID__
// iOS: __APPLE__ && TARGET_OS_IPHONE
#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
주의: _WIN32는 64비트 Windows에서도 정의됩니다. _WIN64만으로 64비트를 구분하는 것은 권장하지 않습니다. _WIN32가 있으면 Windows로 간주합니다.
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. 핵심 구현
3.1 플랫폼별 경로 처리
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// platform_path.hpp — std::filesystem으로 크로스 플랫폼 경로
#include <filesystem>
#include <string>
namespace platform {
inline std::string joinPath(const std::string& a, const std::string& b) {
return (std::filesystem::path(a) / b).string();
}
inline std::string getExecutableDir() {
// C++17 이상: std::filesystem::current_path() 또는
// 플랫폼별 API로 실행 파일 경로 획득
#if defined(_WIN32)
return std::filesystem::current_path().string();
#else
return std::filesystem::current_path().string();
#endif
}
} // namespace platform
실용 팁: std::filesystem::path는 /를 사용해도 Windows에서 \로 변환합니다. path("foo") / "bar"는 모든 플랫폼에서 동작합니다.
3.2 플랫폼별 동적 로딩
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// platform_dlopen.hpp — 플러그인 로드 추상화
#include <string>
#include <functional>
#if defined(_WIN32)
#include <windows.h>
using ModuleHandle = HMODULE;
#define LOAD_LIB(name) LoadLibraryA(name)
#define GET_SYMBOL(handle, name) GetProcAddress(handle, name)
#define FREE_LIB(handle) FreeLibrary(handle)
#else
#include <dlfcn.h>
using ModuleHandle = void*;
#define LOAD_LIB(name) dlopen(name, RTLD_NOW | RTLD_LOCAL)
#define GET_SYMBOL(handle, name) dlsym(handle, name)
#define FREE_LIB(handle) dlclose(handle)
#endif
// 플랫폼별 라이브러리 확장자
inline std::string getSharedLibExtension() {
#if defined(_WIN32)
return ".dll";
#elif defined(__APPLE__)
return ".dylib";
#else
return ".so";
#endif
}
3.3 조건부 컴파일 패턴
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 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
}
};
4. 완전한 크로스 플랫폼 예제
4.1 최소 CMakeLists.txt (데스크톱 3대)
다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
cmake_minimum_required(VERSION 3.16)
project(CrossPlatformApp LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
# 플랫폼별 라이브러리
if(WIN32)
# Windows: ws2_32 (소켓)
set(PLATFORM_LIBS ws2_32)
elseif(UNIX AND NOT APPLE)
# Linux: pthread
find_package(Threads REQUIRED)
set(PLATFORM_LIBS Threads::Threads)
elseif(APPLE)
find_package(Threads REQUIRED)
set(PLATFORM_LIBS Threads::Threads)
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()
4.2 전체 동작 예제: main.cpp
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// main.cpp — 실행 시 플랫폼 정보 출력
#include <iostream>
#include <filesystem>
#include <string>
#if defined(_WIN32)
#define PLATFORM_NAME "Windows"
#elif defined(__APPLE__)
#define PLATFORM_NAME "macOS"
#elif defined(__linux__)
#define PLATFORM_NAME "Linux"
#else
#define PLATFORM_NAME "Unknown"
#endif
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";
return 0;
}
4.3 iOS 툴체인 파일
아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# toolchains/ios.toolchain.cmake (예시)
set(CMAKE_SYSTEM_NAME iOS)
set(CMAKE_OSX_ARCHITECTURES "arm64" CACHE STRING "")
set(CMAKE_OSX_ARCHITECTURES "${CMAKE_OSX_ARCHITECTURES}" CACHE STRING "iOS architectures")
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")
실제 사용: cmake -B build-ios -DCMAKE_TOOLCHAIN_FILE=toolchains/ios.toolchain.cmake
4.4 Android NDK 툴체인
아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Android 빌드 예시
# cmake -B build-android \
# -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
# -DANDROID_ABI=arm64-v8a \
# -DANDROID_PLATFORM=android-21
4.5 플랫폼 추상화 레이어 예제
다음은 cpp를 활용한 상세한 구현 코드입니다. 필요한 모듈을 import하고, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// file_platform.hpp — 파일 열기 추상화
#include <string>
#include <fstream>
#include <filesystem>
namespace platform {
inline bool fileExists(const std::string& path) {
return std::filesystem::exists(path);
}
inline std::string readFile(const std::string& path) {
std::ifstream f(path);
if (!f) return "";
return std::string((std::istreambuf_iterator<char>(f)),
std::istreambuf_iterator<char>());
}
} // namespace platform
5. 자주 발생하는 에러와 해결법
에러 1: “fatal error: ‘unistd.h’ file not found” (Windows)
원인: unistd.h는 POSIX 전용 헤더입니다. Windows(MSVC)에는 없습니다.
해결:
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 잘못된 코드
#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)
원인: dlopen는 libdl에 있습니다. 링크하지 않으면 에러가 납니다.
해결:
다음은 간단한 cmake 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# CMakeLists.txt
if(UNIX AND NOT APPLE)
target_link_libraries(app PRIVATE dl)
endif()
참고: find_package(Threads)처럼 find_library(DL_LIBRARY dl)로 찾을 수도 있습니다.
에러 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는 기본적으로 모든 심볼이 숨겨져 있습니다. DLL을 내보내려면 __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: Windows에서 “LNK2019: unresolved external symbol”
원인: 라이브러리를 링크하지 않았거나, __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();
# DLL 빌드 시
add_library(mylib SHARED mylib.cpp)
target_compile_definitions(mylib PRIVATE MYLIB_EXPORTS)
에러 6: 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: MinGW에서 “undefined reference to WinMain”
원인: Windows GUI 앱이 아닌데 -mwindows로 링크했거나, 콘솔 앱인데 진입점이 잘못 설정되었습니다.
해결:
아래 코드는 cmake를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 콘솔 앱인 경우
add_executable(app WIN32 main.cpp) # WIN32 제거
# 또는
add_executable(app main.cpp)
# -mwindows 사용하지 않음
에러 8: 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")
6. 모범 사례
6.1 플랫폼별 코드 최소화
다음은 cpp를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 나쁜 예: 플랫폼별 코드가 산재
void doWork() {
#ifdef _WIN32
// Windows 전용 50줄
#else
// Unix 전용 50줄
#endif
}
// ✅ 좋은 예: 추상화 레이어로 분리
void doWork() {
platform::init();
platform::execute();
platform::cleanup();
}
6.2 std::filesystem 사용
아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 나쁜 예
std::string path = "config" + std::string(1, '/') + "file.txt"; // Windows에서 문제
// ✅ 좋은 예
auto path = std::filesystem::path("config") / "file.txt";
6.3 CI에서 멀티 플랫폼 빌드
아래 코드는 yaml를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# GitHub Actions 예시
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
6.4 툴체인 파일 분리
아래 코드는 text를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
project/
├── toolchains/
│ ├── linux.toolchain.cmake
│ ├── mingw.toolchain.cmake
│ ├── ios.toolchain.cmake
│ └── android.toolchain.cmake
├── CMakeLists.txt
└── src/
6.5 헤더 가드와 플랫폼 매크로
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 나쁜 예: 플랫폼별 헤더를 조건 없이 include
#include <windows.h> // Linux에서 컴파일 실패
// ✅ 좋은 예: 조건부 include
#if defined(_WIN32)
#include <windows.h>
#else
#include <dlfcn.h>
#include <unistd.h>
#endif
6.6 정수 타입 크기
아래 코드는 cpp를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 플랫폼마다 long 크기가 다를 수 있음 (Windows: 4바이트, Linux 64비트: 8바이트)
// ✅ 고정 크기 타입 사용
#include <cstdint>
int64_t value; // 항상 8바이트
uint32_t count; // 항상 4바이트
6.7 빌드 검증 시퀀스
다음은 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: 빌드 결과
7. 프로덕션 패턴
7.1 통합 빌드 스크립트
다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#!/bin/bash
# build-all.sh — 모든 플랫폼 빌드 (Linux/macOS에서 실행)
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/mingw.cmake
cmake --build $BUILD_DIR/windows
;;
macos)
cmake -B $BUILD_DIR/macos -DCMAKE_BUILD_TYPE=Release
cmake --build $BUILD_DIR/macos
;;
esac
done
7.2 버전·플랫폼 정보 주입
다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# 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}"
)
7.3 플랫폼별 패키징
아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# CMakeLists.txt
if(WIN32)
install(TARGETS app RUNTIME DESTINATION bin)
install(FILES $<TARGET_FILE:mylib> DESTINATION bin)
elseif(APPLE)
install(TARGETS app RUNTIME DESTINATION bin)
install(FILES $<TARGET_FILE:mylib> DESTINATION lib)
else()
install(TARGETS app RUNTIME DESTINATION bin)
install(FILES $<TARGET_FILE:mylib> DESTINATION lib)
endif()
7.4 테스트 매트릭스
# 테스트: 각 플랫폼에서 동일 테스트 실행
enable_testing()
add_test(NAME PlatformTest COMMAND app --test)
7.5 플랫폼별 설치 경로
아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# GNU 스타일 설치 레이아웃 (Linux/macOS)
if(UNIX)
set(CMAKE_INSTALL_PREFIX "/usr/local" CACHE PATH "")
set(CMAKE_INSTALL_BINDIR "bin")
set(CMAKE_INSTALL_LIBDIR "lib")
set(CMAKE_INSTALL_INCLUDEDIR "include")
endif()
# Windows: Program Files 또는 사용자 지정
if(WIN32)
set(CMAKE_INSTALL_PREFIX "C:/Program Files/MyApp" CACHE PATH "")
endif()
7.6 CPack 패키징 (크로스 플랫폼)
다음은 cmake를 활용한 상세한 구현 코드입니다. 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
# CMakeLists.txt 하단에 추가
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_NAME "MyApp")
set(CPACK_PACKAGE_VERSION "1.0.0")
set(CPACK_PACKAGE_VENDOR "MyCompany")
if(WIN32)
set(CPACK_GENERATOR "NSIS;ZIP")
elseif(APPLE)
set(CPACK_GENERATOR "DragNDrop;TGZ")
else()
set(CPACK_GENERATOR "DEB;RPM;TGZ")
endif()
include(CPack)
7.7 환경 변수 기반 툴체인 선택
다음은 bash를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
#!/bin/bash
# build.sh — 환경 변수로 타겟 선택
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. 성능 최적화
8.1 플랫폼별 최적화 플래그
아래 코드는 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.2 조건부 기능 활성화
다음은 간단한 cmake 코드 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 플랫폼별로 불필요한 기능 비활성화
if(NOT WIN32)
add_definitions(-DHAVE_POSIX_SIGNALS=1)
endif()
8.3 크로스 컴파일 시 네이티브 타겟 빌드
아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# Android 빌드 시 호스트 도구(native)도 함께 빌드
if(CMAKE_BUILD_TYPE STREQUAL "Release" AND ANDROID)
add_executable(host_tool host_main.cpp)
set_target_properties(host_tool PROPERTIES
EXCLUDE_FROM_ALL TRUE
)
endif()
8.4 LTO(Link Time Optimization) 플랫폼별 설정
아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
# 릴리스 빌드에서 LTO 활성화
if(CMAKE_BUILD_TYPE STREQUAL "Release")
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported)
if(ipo_supported)
set_property(TARGET app PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
endif()
8.5 플랫폼별 디버그 심볼
아래 코드는 cmake를 사용한 구현 예제입니다. 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
if(MSVC)
target_compile_options(app PRIVATE /Zi)
target_link_options(app PRIVATE /DEBUG)
else()
target_compile_options(app PRIVATE -g)
endif()
9. 정리
| 항목 | 설명 |
|---|---|
| 플랫폼 감지 | _WIN32, __linux__, __APPLE__ 등 매크로 |
| 경로 | std::filesystem::path로 크로스 플랫폼 |
| 동적 로딩 | LoadLibrary/dlopen 추상화 |
| CMake | WIN32, APPLE, UNIX 변수 |
| 툴체인 | -DCMAKE_TOOLCHAIN_FILE로 크로스 컴파일 |
| 에러 | unistd.h·dlopen·DLL 경로·visibility |
| 핵심 원칙: |
- 플랫폼별 코드 최소화: 추상화 레이어로 분리
- 표준 라이브러리 우선:
std::filesystem,std::thread - CMake 툴체인 활용: iOS·Android·MinGW 등
- CI에서 멀티 플랫폼 빌드: 매트릭스 빌드로 검증
구현 체크리스트
-
std::filesystem으로 경로 처리 - 플랫폼별 매크로로
#ifdef분기 -
target_link_libraries에 플랫폼별 라이브러리 추가 - 동적 로딩 시 확장자(
.dll/.so/.dylib) 분기 -
extern "C"로 ABI 안정성 확보 (플러그인·DLL) - iOS·Android 툴체인 파일 준비
- CI에서 Windows·Linux·macOS 빌드 검증
자주 묻는 질문 (FAQ)
Q. 이 내용을 실무에서 언제 쓰나요?
A. 크로스 플랫폼 라이브러리, 멀티 OS 지원 앱, 모바일 게임 등에 활용합니다. 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.
Q. 선행으로 읽으면 좋은 글은?
A. 각 글 하단의 이전 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.
Q. 더 깊이 공부하려면?
A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.
Q. vcpkg·Conan과 크로스 플랫폼 빌드를 함께 쓰려면?
A. vcpkg는 vcpkg install --triplet x64-windows 등으로 타겟을 지정합니다. Conan은 conan install . -s os=Linux -s arch=x86_64처럼 프로필로 지정합니다. CMake에서 find_package로 찾을 때 툴체인과 일치하는 triplet·프로필을 사용해야 합니다.