x64 ABI 규칙 개요
이 항목에서는 x86 아키텍처에 대한 64비트 확장인 x64용 기본 ABI(애플리케이션 이진 인터페이스)에 대해 설명합니다. 호출 규칙, 형식 레이아웃, 스택 및 레지스터 사용과 같은 항목을 다룹니다.
x64 호출 규칙
x86과 x64의 두 가지 중요한 차이점은 다음과 같습니다.
- 64비트 주소 지정 기능
- 일반 용도로 사용하는 16개의 64비트 레지스터입니다.
확장된 레지스터 집합이 지정된 경우 x64는 __fastcall 호출 규칙 및 RISC 기반 예외 처리 모델을 사용합니다.
__fastcall
규칙은 처음 4개 인수에 레지스터를 사용하고 스택 프레임을 사용하여 더 많은 인수를 전달합니다. 레지스터 사용, 스택 매개 변수, 반환 값, 스택 해제 등 x64 호출 규칙에 대한 자세한 내용은 x64 호출 규칙을 참조하세요.
__vectorcall
호출 규칙에 대한 자세한 내용은 __vectorcall
을 참조하세요.
x64 컴파일러 최적화 사용
다음 컴파일러 옵션은 x64용 애플리케이션을 최적화하는 데 도움이 됩니다.
x64 형식 및 저장소 레이아웃
이 섹션에서는 x64 아키텍처용 데이터 형식의 저장소에 대해 설명합니다.
스칼라 형식
모든 맞춤을 사용하여 데이터에 액세스할 수 있지만, 성능 손실을 방지하기 위해 해당 자연 경계 또는 자연 경계의 배수에서 데이터를 맞추세요. 열거형은 상수 정수이며 32비트 정수로 처리됩니다. 다음 표에서는 다음 맞춤 값을 사용하여 맞춤과 관련된 데이터의 형식 정의 및 권장 스토리지에 대해 설명합니다.
- 바이트 - 8비트
- 단어 -16비트
- 2배 단어 - 32비트
- 4배 단어 - 64비트
- 8배 단어 - 128비트
스칼라 형식 | C 데이터 형식 | 저장소 크기(바이트) | 권장 맞춤 |
---|---|---|---|
INT8 |
char |
1 | Byte |
UINT8 |
unsigned char |
1 | Byte |
INT16 |
short |
2 | Word |
UINT16 |
unsigned short |
2 | Word |
INT32 |
int : long |
4 | 2배 단어 |
UINT32 |
unsigned int : unsigned long |
4 | 2배 단어 |
INT64 |
__int64 |
8 | 4배 단어 |
UINT64 |
unsigned __int64 |
8 | 4배 단어 |
FP32 (단정밀도) |
float |
4 | 2배 단어 |
FP64 (배정밀도) |
double |
8 | 4배 단어 |
POINTER |
* | 8 | 4배 단어 |
__m64 |
struct __m64 |
8 | 4배 단어 |
__m128 |
struct __m128 |
16 | 8배 단어 |
x64 집계 및 공용 구조체 레이아웃
배열, 구조체, 공용 구조체와 같은 다른 형식에는 일관성 있는 집계 및 공용 구조체 스토리지와 데이터 검색을 보장하는 보다 엄격한 맞춤 요구 사항이 있습니다. 배열, 구조체 및 공용 구조체에 대한 정의는 다음과 같습니다.
배열
인접한 데이터 개체의 정렬된 그룹을 포함합니다. 각 개체를 요소라고 합니다. 배열 내의 모든 요소는 크기 및 데이터 형식이 동일합니다.
구조체
데이터 개체의 정렬된 그룹을 포함합니다. 배열의 요소와 달리 구조체의 멤버는 서로 다른 데이터 형식 및 크기를 가질 수 있습니다.
Union
명명된 멤버 집합 중 하나를 보유하는 개체입니다. 명명된 집합의 멤버는 임의의 형식일 수 있습니다. 공용 구조체에 할당된 스토리지는 해당 공용 구조체의 가장 큰 멤버에 필요한 스토리지와 맞춤에 필요한 패딩을 합한 것과 같습니다.
다음 표에서는 공용 구조체 및 구조체의 스칼라 멤버에 권장되는 맞춤을 보여 줍니다.
스칼라 형식 | C 데이터 형식 | 필요한 맞춤 |
---|---|---|
INT8 |
char |
Byte |
UINT8 |
unsigned char |
Byte |
INT16 |
short |
Word |
UINT16 |
unsigned short |
Word |
INT32 |
int : long |
2배 단어 |
UINT32 |
unsigned int : unsigned long |
2배 단어 |
INT64 |
__int64 |
4배 단어 |
UINT64 |
unsigned __int64 |
4배 단어 |
FP32 (단정밀도) |
float |
2배 단어 |
FP64 (배정밀도) |
double |
4배 단어 |
POINTER |
* | 4배 단어 |
__m64 |
struct __m64 |
4배 단어 |
__m128 |
struct __m128 |
8배 단어 |
다음 집계 맞춤 규칙이 적용됩니다.
배열의 맞춤은 배열 요소 중 하나의 맞춤과 동일합니다.
구조체 또는 공용 구조체의 시작 부분의 맞춤은 개별 멤버의 최대 맞춤입니다. 구조체 또는 공용 구조체 내의 각 멤버는 이전 표에 정의된 적절한 맞춤에 배치해야 합니다. 이전 멤버에 따라 암시적 내부 패딩이 필요할 수 있습니다.
구조체 크기는 해당 맞춤의 정수배여야 합니다. 마지막 멤버 뒤에 패딩이 필요할 수 있습니다. 구조체와 공용 구조체를 배열로 그룹화할 수 있으므로 구조체 또는 공용 구조체의 각 배열 요소는 이전에 결정된 적절한 맞춤에서 시작하고 끝나야 합니다.
이전 규칙이 유지되는 한 맞춤 요구 사항보다 더 크게 데이터를 맞추는 것이 가능합니다.
개별 컴파일러는 크기 때문에 구조체의 압축을 조정할 수 있습니다. 예를 들어 /Zp(구조체 멤버 맞춤)를 사용하여 구조체의 압축을 조정할 수 있습니다.
x64 구조체 맞춤 예제
다음 네 가지 예제는 각각 구조체 또는 공용 구조체 맞춤을 선언하고 해당 그림은 메모리에서 해당 구조체 또는 공용 구조체의 레이아웃을 보여 줍니다. 그림의 각 열은 메모리 바이트를 나타내며 열의 숫자는 해당 바이트의 변위를 나타냅니다. 각 그림의 두 번째 행에 있는 이름은 선언의 변수 이름에 해당합니다. 음영 처리된 열은 지정된 맞춤을 구현하는 데 필요한 패딩을 표시합니다.
예 1
// Total size = 2 bytes, alignment = 2 bytes (word).
_declspec(align(2)) struct {
short a; // +0; size = 2 bytes
}
예제 2
// Total size = 24 bytes, alignment = 8 bytes (quadword).
_declspec(align(8)) struct {
int a; // +0; size = 4 bytes
double b; // +8; size = 8 bytes
short c; // +16; size = 2 bytes
}
예 3
// Total size = 12 bytes, alignment = 4 bytes (doubleword).
_declspec(align(4)) struct {
char a; // +0; size = 1 byte
short b; // +2; size = 2 bytes
char c; // +4; size = 1 byte
int d; // +8; size = 4 bytes
}
예시 4
// Total size = 8 bytes, alignment = 8 bytes (quadword).
_declspec(align(8)) union {
char *p; // +0; size = 8 bytes
short s; // +0; size = 2 bytes
long l; // +0; size = 4 bytes
}
비트 필드
구조체 비트 필드는 64비트로 제한되며, signed int, unsigned int, int64 또는 unsigned int64 형식일 수 있습니다. 형식 경계를 넘는 비트 필드는 비트 필드를 다음 형식 맞춤에 맞추는 비트를 건너뜁니다. 예를 들어 정수 비트 필드는 32비트 경계를 넘으면 안 됩니다.
x86 컴파일러와 충돌
x86 컴파일러를 사용하여 애플리케이션을 컴파일할 때 4바이트보다 큰 데이터 형식은 스택에서 자동으로 맞추지 않습니다. x86 컴파일러의 아키텍처는 4바이트 맞춤 스택이기 때문에 4바이트보다 큰 값(예: 64비트 정수)을 자동으로 8바이트 주소에 맞출 수 없습니다.
맞추지 않은 데이터로 작업하는 경우 두 가지 의미가 내포됩니다.
맞춘 위치에 액세스하는 것보다 맞추지 않은 위치에 액세스하는 데 시간이 더 오래 걸릴 수 있습니다.
맞추지 않은 위치는 연동 작업에서 사용할 수 없습니다.
보다 엄격한 맞춤이 필요한 경우 변수 선언에 __declspec(align(N))
을 사용합니다. 이렇게 하면 컴파일러가 스택을 동적으로 맞춰 사양을 충족합니다. 그러나 런타임에 스택을 동적으로 조정하면 애플리케이션 실행 속도가 느려질 수 있습니다.
x64 레지스터 사용
x64 아키텍처는 범용 레지스터 16개(이하 '정수 레지스터')와 부동 소수점용으로 사용 가능한 XMM/YMM 레지스터 16개를 제공합니다. 휘발성 레지스터는 호출자가 호출 중에 제거되는 것으로 가정하는 스크래치 레지스터입니다. 함수 호출 중에 값을 유지하려면 비휘발성 레지스터가 필요합니다. 호출 수신자는 비휘발성 레지스터(사용하는 경우)를 저장해야 합니다.
레지스터 변동성 및 보존
다음 테이블에서는 함수 호출에서 각 레지스터가 사용되는 방법을 설명합니다.
등록 | 상태 | 기능 |
---|---|---|
RAX | 휘발성 | 반환 값 레지스터 |
RCX | 휘발성 | 첫 번째 정수 인수 |
RDX | 휘발성 | 두 번째 정수 인수 |
R8 | 휘발성 | 세 번째 정수 인수 |
R9 | 휘발성 | 네 번째 정수 인수 |
R10:R11 | 휘발성 | 호출자가 필요에 따라 보존해야 하며 syscall/sysret 명령에 사용됨 |
R12:R15 | 비휘발성 | 호출 수신자가 보존해야 함 |
RDI | 비휘발성 | 호출 수신자가 보존해야 함 |
RSI | 비휘발성 | 호출 수신자가 보존해야 함 |
RBX | 비휘발성 | 호출 수신자가 보존해야 함 |
RBP | 비휘발성 | 프레임 포인터로 사용 가능(호출 수신자가 보존해야 함) |
RSP | 비휘발성 | 스택 포인터 |
XMM0, YMM0 | 휘발성 | 첫 번째 FP 인수(__vectorcall 사용 시에는 첫 번째 벡터 형식 인수) |
XMM1, YMM1 | 휘발성 | 두 번째 FP 인수(__vectorcall 사용 시에는 두 번째 벡터 형식 인수) |
XMM2, YMM2 | 휘발성 | 세 번째 FP 인수(__vectorcall 사용 시에는 세 번째 벡터 형식 인수) |
XMM3, YMM3 | 휘발성 | 네 번째 FP 인수(__vectorcall 사용 시에는 네 번째 벡터 형식 인수) |
XMM4, YMM4 | 휘발성 | 호출자가 필요에 따라 보존해야 함(__vectorcall 사용 시에는 다섯 번째 벡터 형식 인수) |
XMM5, YMM5 | 휘발성 | 호출자가 필요에 따라 보존해야 함(__vectorcall 사용 시에는 여섯 번째 벡터 형식 인수) |
XMM6:XMM15, YMM6:YMM15 | 비휘발성(XMM), 휘발성(YMM의 위쪽 절반) | 호출 수신자가 보존해야 함. YMM 레지스터는 호출자가 필요에 따라 보존해야 함 |
함수 종료 시 및 C 런타임 라이브러리 호출 및 Windows 시스템 호출에 대한 함수 진입에서 CPU 플래그 레지스터의 방향 플래그는 삭제되는 것으로 예상됩니다.
스택 사용
x64의 스택 할당, 맞춤, 함수 형식 및 스택 프레임에 대한 자세한 내용은 x64 스택 사용을 참조하세요.
프롤로그 및 에필로그
스택 공간을 할당하거나, 다른 함수를 호출하거나, 비휘발성 레지스터를 저장하거나, 예외 처리를 사용하는 모든 함수는 해당 함수 테이블 항목에 연결된 해제 데이터에 주소 제한이 설명된 프롤로그 그리고 각 함수가 종료할 때마다 에필로그가 있어야 합니다. x64에서 필요한 프롤로그 및 에필로그 코드에 대한 자세한 내용은 x64 프롤로그 및 에필로그를 참조하세요.
x64 예외 처리
x64에서 구조적 예외 처리 및 C++ 예외 처리 동작을 구현하는 데 사용되는 규칙 및 데이터 구조에 대한 자세한 내용은 x64 예외 처리를 참조하세요.
내장 함수 및 인라인 어셈블리
x64 컴파일러의 제약 조건 중 하나는 인라인 어셈블러를 지원하지 않는 것입니다. 즉, C 또는 C++로 작성할 수 없는 함수를 컴파일러에서 지원하는 서브루틴 또는 내장 함수로 작성해야 합니다. 일부 함수는 성능이 중요하지만 다른 함수는 그렇지 않습니다. 성능에 민감한 함수는 내장 함수로 구현해야 합니다.
컴파일러에서 지원하는 내장 함수는 컴파일러 내장 함수에 설명되어 있습니다.
x64 이미지 형식
x64 실행 가능 이미지 형식은 PE32+입니다. 실행 가능 이미지(DLL 및 EXE)는 최대 2기가바이트로 제한되므로 32비트 치환을 사용한 상대 주소 지정을 사용하여 정적 이미지 데이터를 처리할 수 있습니다. 이 데이터에는 가져오기 주소 테이블, 문자열 상수, 정적 전역 데이터 등이 포함됩니다.