C++은 수동 메모리 관리를 갖춘 멀티 패러다임 프로그래밍 언어로, 절차적, 객체 지향 및 제네릭 프로그래밍을 결합합니다. 모바일 개발에서 C++는 Android NDK를 통한 라이브러리 포팅과 Unity 및 Unreal Engine 게임 엔진을 통한 크로스 플랫폼 게임 제작에 사용됩니다. TIOBE (2026)에 따르면 C++는 전문 개발자 사이에서 상위 5개 가장 인기 있는 언어 중 하나입니다.
핵심 요점
C++은 1985년 비야네 스트로스트룹이 C 언어의 확장으로 클래스 지원을 포함하여 만든 프로그래밍 언어입니다. C++은 포인터를 통한 직접 메모리 액세스, 템플릿 메타프로그래밍 및 다중 상속을 제공합니다. C++17 및 C++20 표준은 코루틴, 개념(concepts) 및 범위(ranges)를 추가했습니다.
Java 및 Kotlin과 달리 C++에는 가비지 컬렉터가 없습니다 — 개발자가 new 및 delete 연산자를 통해 수동으로 메모리 할당 및 해제를 관리합니다. 이는 성능에 대한 완전한 제어를 제공하지만 규율이 필요합니다. JetBrains Developer Survey (2025)에 따르면 C++ 개발자의 42%가 게임 산업에서 이 언어를 사용합니다.
C++ 표준은 3년마다 발전합니다: C++11 (람다, auto, unique_ptr), C++14 (제네릭 람다), C++17 (if constexpr, filesystem), C++20 (모듈, 코루틴, 개념). Clang 및 GCC 컴파일러는 모든 최신 표준을 지원하며, Android NDK는 LLVM 툴체인의 Clang을 사용합니다.
C++와 JVM 언어의 주요 차이점은 수동 메모리 관리와 기계어로의 직접 컴파일입니다. Java와 Kotlin은 자동 가비지 컬렉션을 갖춘 가상 머신에서 실행됩니다. C++ 코드는 특정 아키텍처(ARM, x86 또는 RISC-V)용 네이티브 바이너리로 컴파일됩니다.
| 기준 | C++ | Java / Kotlin |
|---|---|---|
| 메모리 관리 | 수동 (new/delete, RAII) | 자동 (가비지 컬렉터) |
| 컴파일 | 네이티브 기계어 | JVM / ART 바이트코드 |
| 성능 | 최대 (zero-cost abstractions) | GC 일시 중지와 함께 높음 |
| 모바일 개발에서의 사용 | NDK, 게임, 라이브러리 | UI 앱, 비즈니스 로직 |
| 빌드 | CMake, NDK Build, Gradle + NDK | Gradle, Maven |
| 크로스 플랫폼 | 각 플랫폼용으로 컴파일 | 어디서나 JVM / ART |
Android NDK (Native Development Kit)는 Android 애플리케이션의 일부를 C 및 C++로 작성하기 위한 Google의 툴킷입니다. NDK에는 Clang 기반 툴체인, Android 시스템 헤더 파일, 디버깅 및 프로파일링 라이브러리가 포함됩니다. 버전 r27 (2025)은 C++20을 지원하고 LLVM 18을 사용합니다.
NDK는 C++ 코드를 ARM64, ARM32, x86 및 x86_64 아키텍처용 동적 .so 라이브러리로 컴파일합니다. Java 또는 Kotlin 애플리케이션은 System.loadLibrary()를 통해 이러한 라이브러리를 로드하고 JNI를 통해 네이티브 함수를 호출합니다. Google에 따르면 NDK는 500만 회 이상 다운로드되었습니다.
일반적인 Android NDK 프로젝트에는 C++ 소스 파일이 있는 app/src/main/cpp/ 폴더, 빌드용 CMakeLists.txt 및 네이티브 메서드를 선언하는 Java 클래스가 포함됩니다. Gradle은 build.gradle의 externalNativeBuild 블록을 통해 NDK를 연결합니다.
// native-lib.cpp — Android용 간단한 JNI 메서드
#include <jni.h>
#include <string>
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_app_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /*thiz*/) {
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}코드는 Java_com_example_app_MainActivity_stringFromJNI라는 전체 이름으로 JNI 함수를 선언합니다 — 이것이 JNI 명명 규칙입니다. 함수는 JNIEnv(실행 환경)와 jobject(호출 클래스 인스턴스)에 대한 포인터를 받습니다. env->NewStringUTF는 C++ 문자열에서 Java 문자열을 만듭니다.
Android는 여러 ABI(애플리케이션 바이너리 인터페이스)를 지원합니다: arm64-v8a(64비트 ARM 기기, 최신 스마트폰의 99%), armeabi-v7a(32비트 ARM, 레거시 기기), x86_64(에뮬레이터). NDK는 build.gradle의 ABI 필터를 통해 각 아키텍처용 .so를 빌드할 수 있습니다.
android {
defaultConfig {
ndk {
abiFilters "arm64-v8a", "armeabi-v7a"
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
version "3.22.1"
}
}
}C++은 게임 엔진의 주요 언어입니다. Unreal Engine은 모든 게임 코드에 C++를 사용하고, Unity는 렌더링 및 물리학을 위해 엔진 코어에서 C++를 사용합니다. 개발자가 Unity에서 C#으로 작성하더라도 내부적으로 C++가 그래픽, 사운드 및 물리 계산을 관리합니다.
Unreal Engine은 처음부터 C++로 구축되었습니다. 게임 로직 코드는 Blueprints(비주얼 스크립팅 시스템)와의 통합을 위해 UHT(Unreal Header Tool) 매크로를 사용하여 C++로 작성됩니다. 클래스는 접두사 문자 U, A, F와 함께 기본 유형 AActor, UObject, UGameInstance에서 상속됩니다.
// Unreal Engine에서 C++를 사용한 캐릭터 클래스
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Character.h"
#include "MyCharacter.generated.h"
UCLASS()
class AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
AMyCharacter();
virtual void BeginPlay() override;
virtual void Tick(float DeltaTime) override;
UPROPERTY(EditAnywhere, Category = "Movement")
float MoveSpeed = 600.0f;
protected:
void SetupPlayerInputComponent(UInputComponent *PlayerInputComponent) override;
};UCLASS() 및 UPROPERTY() 매크로는 Unreal Engine 리플렉션 시스템의 일부입니다. UHT(Unreal Header Tool)는 직렬화, 블루프린트 및 네트워크 복제를 위해 헤더 파일에서 C++ 코드를 생성합니다. MoveSpeed는 UPROPERTY(EditAnywhere)를 통해 에디터에 표시됩니다.
Unity는 게임 로직에 C#을 사용하지만, C++는 네이티브 플러그인(Android용 .so 라이브러리 및 iOS용 .bundle)에서 사용됩니다. Android NDK를 통해 광고 네트워크, 분석용 C++ SDK를 통합하거나 데스크톱 게임의 C++ 라이브러리를 포팅할 수 있습니다.
using System.Runtime.InteropServices;
using UnityEngine;
public class NativeBridge : MonoBehaviour
{
const string LIB_NAME = "native_plugin";
[DllImport(LIB_NAME, CallingConvention = CallingConvention.Cdecl)]
private static extern int ProcessFrame(
int width, int height,
IntPtr pixelData
);
void Update()
{
int result = ProcessFrame(Screen.width, Screen.height, IntPtr.Zero);
Debug.Log("Frame processed: " + result);
}
}이 예제에서 C#은 P/Invoke를 통해 C++ 함수 ProcessFrame에 대한 호출을 마샬링합니다. DllImport 특성은 .so 라이브러리의 이름을 지정합니다. C++ 함수는 실시간으로 프레임을 처리하고 결과를 반환합니다. 이 접근 방식은 포스트 이펙트 및 컴퓨터 비전에 사용됩니다.
JNI (Java Native Interface)는 Java 또는 Kotlin에서 C/C++ 코드를 호출하기 위한 표준 메커니즘입니다. Android에서 JNI는 ART 가상 머신과 네이티브 라이브러리 간에 데이터를 전송하는 유일한 방법입니다. 각 JNI 호출에는 오버헤드가 있으므로 데이터는 한 번에 하나의 필드가 아닌 배치로 전송해야 합니다.
JNIEnv는 JNI 함수 테이블에 대한 포인터입니다. env->FindClass는 Java 클래스를 로드하고, env->GetMethodID는 메서드 식별자를 얻으며, env->CallVoidMethod는 이를 호출합니다. 문자열에는 GetStringUTFChars 및 ReleaseStringUTFChars를 사용합니다. JNI는 Java 객체에 대한 전역 및 약한 참조도 지원합니다.
// JNI를 통해 C++에서 Java로 바이트 배열 전달
extern "C" JNIEXPORT void JNICALL
Java_com_example_app_ImageProcessor_processArray(
JNIEnv *env, jobject /*thiz*/,
jbyteArray input, jint length) {
jbyte *buffer = env->GetByteArrayElements(input, nullptr);
jsize size = env->GetArrayLength(input);
for (jsize i = 0; i < size; ++i) {
buffer[i] = buffer[i] ^ 0xFF; // 바이트 반전
}
env->ReleaseByteArrayElements(input, buffer, JNI_COMMIT);
}GetByteArrayElements는 Java 바이트 배열에 대한 포인터를 얻습니다. ReleaseByteArrayElements의 JNI_COMMIT은 메모리를 해제하지 않고 변경 사항을 Java 배열에 다시 복사합니다. for 루프 처리는 GC 일시 중지 없이 최대 C++ 성능으로 CPU에서 실행됩니다.
JNI의 주요 위험은 전역 참조 누수입니다. 모든 NewGlobalRef는 DeleteGlobalRef를 통해 해제되어야 합니다. 로컬 참조(FindClass, NewStringUTF가 반환하는)는 네이티브 함수에서 반환될 때 자동으로 해제됩니다. 집중적인 호출의 경우 PushLocalFrame / PopLocalFrame을 사용하세요.
CMake는 Android NDK에서 C++의 주요 빌드 도구입니다. CMakeLists.txt는 빌드 대상, 라이브러리 및 종속성을 설명합니다. Gradle은 externalNativeBuild를 통해 CMake를 호출하여 NDK 및 툴체인 경로를 전달합니다. 대안으로 Android.mk 및 Application.mk 기반의 NDK Build가 있습니다.
CMake 3.22+는 다양한 구성(디버그, 릴리스, 프로필)에 대해 다른 최적화 플래그를 가진 프리셋(CMakePresets.json)을 지원합니다. Android의 경우 NDK의 android.toolchain.cmake 툴체인이 사용되며, 빌드 중 Gradle이 자동으로 제공합니다.
# Android NDK 프로젝트용 CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project("native-lib")
add_library(
native-lib
SHARED
src/main/cpp/native-lib.cpp
src/main/cpp/image_utils.cpp
)
target_include_directories(
native-lib PRIVATE
src/main/cpp/include
)
target_link_libraries(
native-lib
android
log
${CMAKE_SOURCE_DIR}/libs/libjpeg.a
)
set_target_properties(
native-lib PROPERTIES
CXX_STANDARD 20
CXX_STANDARD_REQUIRED ON
)add_library(SHARED)는 .so 라이브러리를 만듭니다. target_link_libraries는 시스템 라이브러리 android(JNI용)와 log(__android_log_print용)를 링크합니다. 정적 라이브러리 libjpeg.a는 JPEG 이미지 처리를 위해 링크됩니다. CXX_STANDARD 20은 C++20 지원을 활성화합니다.
vcpkg는 Android NDK를 대상 플랫폼으로 지원하는 Microsoft의 C++ 패키지 관리자입니다. vcpkg를 통해 Android ABI용으로 컴파일된 Boost, OpenCV, SQLite, nlohmann-json 및 기타 라이브러리를 설치할 수 있습니다. Vcpkg는 CMAKE_TOOLCHAIN_FILE을 통해 CMake와 통합됩니다.
C++은 네이티브 ARM 코드로의 컴파일과 가비지 컬렉터 부재 덕분에 모바일 기기에서 최대 성능을 제공합니다. Google 테스트(Android Performance Patterns, 2024)에 따르면 계산 집약적 작업의 경우 C++의 JNI 코드가 동등한 Java 코드보다 3~10배 빠르게 실행됩니다.
모바일 플랫폼에서 C++ 성능의 핵심 요소: 캐시 지역성(연속 메모리 레이아웃), ARM용 NEON SIMD 명령어, 메시지 큐를 통한 비동기 JNI 통신. NEON은 ARM의 SIMD 확장으로, 단일 명령어로 128비트 데이터를 처리하며 오디오, 비디오 및 이미지 처리에 필수적입니다.
// NEON 내장 함수를 통한 SIMD 배열 처리
#include <arm_neon.h>
void process_audio_neon(float *data, size_t n) {
for (size_t i = 0; i < n; i += 4) {
float32x4_t vec = vld1q_f32(&data[i]);
float32x4_t result = vmulq_f32(vec, vdupq_n_f32(0.5f));
vst1q_f32(&data[i], result);
}
}vld1q_f32는 메모리에서 128비트 NEON 레지스터로 4개의 float를 로드합니다. vmulq_f32는 한 사이클에 4개 요소를 모두 0.5로 곱합니다. vst1q_f32는 결과를 다시 저장합니다. SIMD가 없으면 동일한 루프가 순차적으로 4번의 곱셈을 수행합니다 — NEON은 이러한 연산에 대해 최대 4배의 속도 향상을 제공합니다.
| 연산 | C++ (ms) | Java/Kotlin (ms) | 속도 향상 |
|---|---|---|---|
| 1920x1080 이미지 처리 | 12 | 45 | 3.75x |
| 고속 푸리에 변환 (FFT) | 8 | 34 | 4.25x |
| 1MB 블록 AES-256 암호화 | 3 | 18 | 6x |
| ZLIB 데이터 압축 | 5 | 22 | 4.4x |
첫 번째이자 가장 위험한 실수는 메모리 누수입니다. 모든 new에는 delete가, 모든 new[]에는 delete[]가 있어야 합니다. RAM이 제한된 모바일 기기에서는 누수가 발생하면 OOM(메모리 부족) 및 앱 충돌로 이어집니다. 스마트 포인터(std::unique_ptr, std::shared_ptr, std::weak_ptr)를 통해 RAII를 사용하세요.
두 번째 실수는 ABI 호환성 무시입니다. armeabi-v7a용으로 컴파일된 라이브러리는 ARM64 기기에서 로드되지 않습니다. build.gradle에 항상 abiFilters를 지정하고 x86_64 에뮬레이터뿐만 아니라 실제 기기에서 테스트하세요. 자동 ABI 필터링을 위해 Android App Bundle을 사용하세요.
세 번째 흔한 실수는 빈번한 JNI 호출입니다. Java와 C++ 간의 각 전환에는 약 50~100ns가 소요됩니다. JNI를 통해 10000개 요소의 배열을 하나씩 처리하면 오버헤드가 유용한 작업을 초과합니다. 전체 배열을 한 번에 전달하고 C++ 쪽에서 처리하세요.
스레딩 문제는 또 다른 흔한 실수입니다. C++는 std::thread를 시작할 수 있지만 JNI는 Java가 생성한 스레드에서만 액세스할 수 있습니다. C++ 스레드에서 Java 메서드를 호출하려면 JNI를 통해 AttachCurrentThread / DetachCurrentThread를 사용하세요.
// C++ 스레드에서 Java를 올바르게 호출
JavaVM *g_vm; // JNI_OnLoad에서 얻은 전역 포인터
void background_task() {
JNIEnv *env;
jint res = g_vm->AttachCurrentThread(&env, nullptr);
if (res == JNI_OK) {
jclass clazz = env->FindClass("com/example/App");
jmethodID mid = env->GetStaticMethodID(clazz, "onProgress", "(I)V");
env->CallStaticVoidMethod(clazz, mid, 100);
g_vm->DetachCurrentThread();
}
}자주 묻는 질문
Android NDK (Native Development Kit)는 애플리케이션의 일부를 C 또는 C++로 작성하기 위한 툴킷입니다. 성능이 중요한 작업, C/C++ 라이브러리를 Android로 포팅, Unity 및 Unreal Engine용 크로스 플랫폼 코드 구현에 사용됩니다.
Unity에서 게임 로직 코드는 C#으로 작성되지만, C++는 Android NDK와 함께 플러그인, 하드웨어 가속, 사용자 C++ 라이브러리 포팅, 광고 및 분석용 네이티브 SDK 통합에 사용됩니다.
CMakeLists.txt는 소스 파일, 라이브러리 및 컴파일 플래그를 설명합니다. 최소 구성에는 .so 빌드를 위한 add_library와 Android log 및 기타 시스템 라이브러리 링크를 위한 target_link_libraries가 포함됩니다.
JNI (Java Native Interface)는 Android 앱의 Java/Kotlin 코드와 C++ 라이브러리 사이의 계층입니다. C++가 Java에서 호출되는 경우 JNI는 필수입니다. NDK를 통해 완전히 C++ 코드를 작성할 수 있지만, 진입점은 일반적으로 네이티브 함수를 호출하는 Java Activity입니다.
C++는 게임, 실시간 오디오/비디오 처리, 컴퓨터 비전, 암호화, 물리 시뮬레이션 및 다른 플랫폼의 라이브러리 포팅에 적합합니다. 일반적인 UI 애플리케이션의 경우 Kotlin이 더 효율적이고 간단합니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.