C++ 인라인 어셈블리 | "asm" 키워드 가이드

C++ 인라인 어셈블리 | "asm" 키워드 가이드

이 글의 핵심

인라인 어셈블리(asm)는 C++ 코드 안에 어셈블리를 끼워 넣어 특정 아키텍처 명령을 쓰는 고급 기능입니다. 이 글에서는 GCC·Clang AT&T 문법과 MSVC Intel 문법 차이, 제약과 대안을 예제로 소개합니다.

기본 문법

성능 병목이나 특수 명령이 필요할 때만 인라인 어셈블리를 고려하는 것이 일반적입니다. 이 글에서는 컴파일러별 문법 차이를 알아 두고, 이식성을 잃기 쉬운 부분을 판단하는 데 도움이 되도록 예제를 나열합니다.

GCC/Clang (AT&T 문법)

int main() {
    int x = 10;
    int y = 20;
    int result;
    
    asm("addl %1, %0"
        : "=r" (result)  // 출력
        : "r" (x), "0" (y)  // 입력
    );
    
    cout << result << endl;  // 30
}

MSVC (Intel 문법)

int main() {
    int x = 10;
    int y = 20;
    int result;
    
    __asm {
        mov eax, x
        add eax, y
        mov result, eax
    }
    
    cout << result << endl;  // 30
}

레지스터 제약

int add(int a, int b) {
    int result;
    
    asm("addl %2, %0"
        : "=r" (result)      // 출력: 아무 레지스터
        : "0" (a), "r" (b)   // 입력
    );
    
    return result;
}

// 제약 문자:
// r: 범용 레지스터
// a: %eax/%rax
// b: %ebx/%rbx
// c: %ecx/%rcx
// d: %edx/%rdx
// m: 메모리
// i: 즉시값

실전 예시

예시 1: CPUID

#include <iostream>
using namespace std;

void cpuid(int code, int* a, int* b, int* c, int* d) {
    asm volatile("cpuid"
        : "=a"(*a), "=b"(*b), "=c"(*c), "=d"(*d)
        : "a"(code)
    );
}

int main() {
    int a, b, c, d;
    cpuid(0, &a, &b, &c, &d);
    
    char vendor[13];
    *(int*)(vendor) = b;
    *(int*)(vendor + 4) = d;
    *(int*)(vendor + 8) = c;
    vendor[12] = '\0';
    
    cout << "CPU: " << vendor << endl;
}

예시 2: 원자적 연산

int atomicIncrement(int* ptr) {
    int result;
    
    asm volatile(
        "lock; xaddl %0, %1"
        : "=r" (result), "+m" (*ptr)
        : "0" (1)
        : "memory"
    );
    
    return result;
}

int main() {
    int counter = 0;
    
    for (int i = 0; i < 10; i++) {
        atomicIncrement(&counter);
    }
    
    cout << counter << endl;  // 10
}

예시 3: 타임스탬프 카운터

uint64_t rdtsc() {
    uint32_t lo, hi;
    
    asm volatile("rdtsc"
        : "=a"(lo), "=d"(hi)
    );
    
    return ((uint64_t)hi << 32) | lo;
}

int main() {
    uint64_t start = rdtsc();
    
    // 측정할 코드
    for (int i = 0; i < 1000000; i++) {
        // ...
    }
    
    uint64_t end = rdtsc();
    
    cout << "사이클: " << (end - start) << endl;
}

예시 4: 메모리 배리어

void memoryBarrier() {
    asm volatile("mfence" ::: "memory");
}

void compilerBarrier() {
    asm volatile("" ::: "memory");
}

// 사용
atomic<bool> ready(false);
int data = 0;

void producer() {
    data = 42;
    compilerBarrier();  // 재배치 방지
    ready.store(true, memory_order_release);
}

volatile

int main() {
    int x = 10;
    
    // volatile: 최적화 방지
    asm volatile("nop");  // 제거되지 않음
    
    // 메모리 clobber
    asm volatile("" ::: "memory");  // 메모리 재배치 방지
}

플랫폼별 차이

x86-64

// 64비트 레지스터
asm("movq %0, %%rax" : : "r"(value));

ARM

// ARM 문법
asm("mov r0, %0" : : "r"(value));

크로스 플랫폼

#ifdef __x86_64__
    asm("rdtsc" : "=a"(lo), "=d"(hi));
#elif __aarch64__
    asm("mrs %0, cntvct_el0" : "=r"(cycles));
#else
    #error "Unsupported platform"
#endif

자주 발생하는 문제

문제 1: 레지스터 손상

// ❌ 레지스터 손상
asm("movl $10, %eax");  // eax 손상

// ✅ clobber 명시
asm("movl $10, %%eax"
    :
    :
    : "%eax"  // eax가 손상됨을 명시
);

문제 2: 최적화 간섭

// ❌ 최적화로 제거됨
asm("nop");

// ✅ volatile 사용
asm volatile("nop");

문제 3: 플랫폼 의존성

// ❌ x86 전용
asm("rdtsc" : "=a"(lo), "=d"(hi));

// ✅ 조건부 컴파일
#ifdef __x86_64__
    asm("rdtsc" : "=a"(lo), "=d"(hi));
#else
    // 대체 구현
#endif

인라인 어셈블리 vs Intrinsics

// 인라인 어셈블리
asm("addl %1, %0" : "=r"(result) : "r"(a), "0"(b));

// Intrinsics (권장)
#include <x86intrin.h>
result = _mm_add_epi32(a, b);

Intrinsics 장점:

  • 타입 안전
  • 최적화 가능
  • 플랫폼 독립적 (컴파일러가 변환)

디버깅

// 어셈블리 출력
void func() {
    int x = 10;
    int y = x * 2;
}

// 컴파일
// g++ -S -O2 program.cpp
// program.s 파일 확인

FAQ

Q1: 인라인 어셈블리는 언제 사용하나요?

A:

  • 극한 최적화
  • 하드웨어 직접 접근
  • 특수 명령어 (CPUID, RDTSC)

Q2: Intrinsics vs 인라인 어셈블리?

A: 가능하면 Intrinsics를 사용하세요. 더 안전하고 이식성이 좋습니다.

Q3: 성능 향상이 보장되나요?

A: 아니요. 컴파일러 최적화가 더 나을 수 있습니다.

Q4: 플랫폼 독립적으로 만들려면?

A:

  • 조건부 컴파일
  • Intrinsics 사용
  • 어셈블리는 최후 수단

Q5: 디버깅은?

A:

  • GDB의 disassemble 명령
  • -S 옵션으로 어셈블리 출력
  • Compiler Explorer (godbolt.org)

Q6: 인라인 어셈블리 학습 리소스는?

A:

  • GCC 인라인 어셈블리 문서
  • Intel/AMD 매뉴얼
  • “PC Assembly Language” (Paul Carter)

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

  • C++ inline 함수 | “Inline Function” 가이드
  • C++ 메모리 정렬 | “Alignment와 Padding” 가이드
  • C++ Expression Templates | “지연 평가” 고급 기법

관련 글

  • C++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL |
  • C++ Aggregate Initialization |