홈으로 돌아가기

x86-64 구조체 반환: 레지스터와 MEMORY ABI

이 기사는 x86-64에서 System V ABI에 따라 함수에서 기본 타입과 구조체를 반환하는 메커니즘을 분석합니다. INTEGER 및 MEMORY 분류 규칙, rax/rdx 레지스터 사용 및 숨겨진 rdi 인수를 설명합니다. 최적화 없는 어셈블리 예제.

x86-64: rax, rdx 및 rdi를 통해 구조체 반환 방법
Advertisement 728x90

x86-64 시스템 V ABI에서 값 반환 방식 이해하기

x86-64 아키텍처에서 시스템 V ABI는 첫 번째 두 개의 정수 값을 반환할 때 raxrdx 레지스터를 사용합니다. 구조체의 경우, 크기에 따라 분류 기준이 적용되며, 작은 크기의 경우 INTEGER, 큰 경우 MEMORY로 분류됩니다. 이는 값이 직접 반환되는지, 아니면 rdi 레지스터를 통해 전달되는 숨겨진 포인터를 통해 반환되는지를 결정합니다.

먼저 int 값을 반환하는 기본 사례를 살펴보겠습니다:

one_plus_one():
  mov DWORD PTR [rbp-4], 2
  mov eax, DWORD PTR [rbp-4]
  ret

값은 eax (즉, rax의 하위 32비트)에 로드됩니다. 마찬가지로 64비트 타입의 경우 전체 rax 레지스터를 사용합니다.

Google AdInline article slot

16바이트 이하의 소형 구조체: 레지스터를 통한 반환

소형 구조체는 raxrdx 레지스터를 통해 직접 반환됩니다. 예를 들어 16바이트 크기의 구조체를 고려해 보겠습니다:

struct nums {
    std::int64_t first{};
    std::int64_t second{};
};
nums construct() {
    nums ret{10, 120};
    return ret;
}

생성된 어셈블리 코드는 다음과 같습니다:

construct():
  mov QWORD PTR [rbp-16], 10
  mov QWORD PTR [rbp-8], 120
  mov rax, QWORD PTR [rbp-16]
  mov rdx, QWORD PTR [rbp-8]
  ret

필드는 순차적으로 배치되며, firstrax, secondrdx에 저장됩니다. 메모리 레이아웃은 리틀엔디안 순서를 따르며 스택은 아래로 성장합니다.

Google AdInline article slot
  • INTEGER 분류: 최대 두 개의 64비트 필드까지 레지스터에 맞습니다.
  • 정렬: 필드는 8바이트 경계에 맞춰 정렬됩니다.
  • 제한 조건: 직접 반환을 위해 총 크기는 ≤16바이트여야 합니다.

대용량 구조체: rdi에 숨겨진 인자 전달

16바이트를 초과하는 구조체는 MEMORY로 분류됩니다. 호출자는 메모리를 할당하고 그 주소를 rdi에 전달하여 숨겨진 첫 번째 인자로 전달합니다.

예시: 32바이트 크기의 구조체입니다:

struct many_nums {
    std::int64_t first{};
    std::int64_t second{};
    std::int64_t third{};
    std::int64_t fourth{};
};
many_nums construct_scary() {
    many_nums temp{10, 20, 30, 40};
    return temp;
}

construct_scary 함수 내부에서는 다음과 같이 동작합니다:

Google AdInline article slot
construct_scary():
  mov QWORD PTR [rbp-8], rdi
  mov rax, QWORD PTR [rbp-8]
  mov QWORD PTR [rax], 10
  mov QWORD PTR [rax+8], 20
  mov QWORD PTR [rax+16], 30
  mov QWORD PTR [rax+24], 40
  ret

mov rax, [rbp-8]가 반복되는 것은 -O0 옵션의 산물입니다. 최적화된 코드에서는 rdi를 직접 사용합니다.

main 함수에서는 다음과 같습니다:

main:
  sub rsp, 32
  lea rax, [rbp-32]
  mov rdi, rax
  call construct_scary
  ret
  • 메모리 할당: sub rsp, size 또는 힙/스택을 통해 수행됩니다.
  • 주소 전달: 함수 호출 전에 rdi에 주소를 전달합니다.
  • MEMORY 분류: 시스템 V ABI에서 16바이트 이상이거나 비-INTEGER 타입인 경우 사용됩니다.

반환 값 분류 규칙

시스템 V ABI는 세 가지 범주를 정의합니다:

  • INTEGER: 최대 16바이트, 스칼라 또는 집합형 필드가 ≤2×64비트 → rax/rdx에 반환.
  • MEMORY: 16바이트 이상, SSE 패킹 타입이 >2×128비트, 또는 비-INTEGER 타입 → 숨겨진 rdi 인자 사용.
  • SSE: 부동소수점 값 최대 128비트 → xmm0에 반환 (본 문서에서는 다루지 않음).

GCC 및 Clang과 같은 컴파일러는 이러한 규칙에 따라 코드를 생성합니다. 디버깅 시 중간 단계를 명확히 보기 위해 -O0 옵션을 사용하세요.

핵심 요약

  • 16바이트 이하의 정수 반환에는 rax/rdx를 사용하세요.
  • MEMORY 분류 타입의 경우, 호출자가 rdi를 통해 메모리를 제공해야 합니다.
  • 시스템 V ABI는 리눅스와 맥OS 간 일관된 동작을 보장합니다.
  • 최적화(-O2+)는 불필요한 mov 명령어를 제거합니다.
  • 생성된 코드를 확인하려면 objdump -d 또는 Godbolt Compiler Explorer를 활용하세요.

— Editorial Team

Advertisement 728x90

다음 읽기