맨위로가기

호출 규약

"오늘의AI위키"는 AI 기술로 일관성 있고 체계적인 최신 지식을 제공하는 혁신 플랫폼입니다.
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.

1. 개요

호출 규약은 함수 호출 시 인수를 전달하고 반환 값을 받는 데 사용되는 규칙의 집합이다. 단일 플랫폼에서도 여러 호출 규약이 존재할 수 있으며, 성능, 다른 언어의 규약 적용, 플랫폼 제한 등의 이유로 선택된다. x86, x86-64, ARM, MIPS, SPARC, PowerPC 등의 아키텍처는 각기 다른 호출 규약을 사용하며, 이는 레지스터 사용, 스택 관리, 인수 전달 방식 등에서 차이를 보인다. 언어 사양은 호출 규약에 영향을 미칠 수 있으며, cdecl, stdcall, fastcall, thiscall, Pascal 호출 규약 등이 존재한다. 표준 서브루틴 프롤로그와 에필로그는 함수 호출과 반환을 위한 일반적인 패턴을 제공한다.

더 읽어볼만한 페이지

  • 함수 (프로그래밍) - 사용자 정의 함수
    사용자 정의 함수는 프로그래밍 언어와 데이터베이스 시스템에서 사용자가 직접 정의하여 재사용할 수 있는 코드 블록이다.
  • 함수 (프로그래밍) - 코루틴
    코루틴은 실행을 멈췄다가 다시 시작할 수 있는 서브루틴의 특별한 형태로, 로컬 데이터를 보존하며 다양한 방식으로 구현되고 여러 프로그래밍 상황에서 유용하게 쓰인다.
  • 컴퓨터 프로그래밍 - 순서도
    순서도는 컴퓨터 알고리즘이나 프로세스를 시각적으로 표현하는 도구로, 흐름 공정 차트에서 기원하여 컴퓨터 프로그래밍 분야에서 알고리즘을 설명하는 데 사용되며, 다양한 종류와 소프트웨어 도구가 존재한다.
  • 컴퓨터 프로그래밍 - 의사코드
    의사코드는 컴퓨터 과학 및 수치 계산 분야에서 알고리즘을 설명하기 위해 사용되는 비표준적인 언어로, 자연어와 프로그래밍 언어의 요소를 혼합하여 알고리즘의 논리적 흐름을 이해하기 쉽게 하고 프로그래머가 실제 코드로 구현하기 전에 알고리즘을 설계하고 검토하는 데 유용하다.
  • 컴퓨터에 관한 - 고속 패킷 접속
    고속 패킷 접속(HSPA)은 3세대 이동통신(3G)의 데이터 전송 속도를 높이는 기술 집합체로, 고속 하향/상향 패킷 접속(HSDPA/HSUPA)을 통해 속도를 개선하고 다중 안테나, 고차 변조, 다중 주파수 대역 활용 등의 기술로 진화했으나, LTE 및 5G 기술 발전으로 현재는 상용 서비스가 중단되었다.
  • 컴퓨터에 관한 - 데이터베이스
    데이터베이스는 여러 사용자가 공유하고 사용하는 정보의 집합으로, 데이터베이스 관리 시스템을 통해 접근하며, 검색 및 갱신 효율을 높이기 위해 고도로 구조화되어 있고, 관계형, NoSQL, NewSQL 등 다양한 모델로 발전해왔다.
호출 규약

2. 플랫폼 별 호출 규약

플랫폼별로 다양한 호출 규약이 존재하며, 이는 응용 프로그램 이진 인터페이스(ABI)의 일부로 간주된다.[1] 호출 규약은 함수 호출자와 피호출자 간의 약속으로, 다음과 같은 사항들을 정의한다.


  • 매개변수 위치 (레지스터, 호출 스택, 또는 혼합)
  • 매개변수 전달 순서 (왼쪽에서 오른쪽, 오른쪽에서 왼쪽 등)
  • 가변 인자 함수 처리 방법
  • 반환 값 전달 방법 (스택, 레지스터, 힙 등)
  • 길거나 복잡한 값 처리 방법
  • 피호출자가 보존해야 할 레지스터
  • 함수 호출 설정 및 정리 작업 분담 (특히 스택 프레임 복원)
  • 메타데이터 전달 여부 및 방법
  • 프레임 포인터 이전 값 저장 위치
  • 비지역 데이터 접근을 위한 정적 범위 링크 위치
  • 객체 지향 프로그래밍 언어에서 객체 참조 방법


단일 플랫폼에서도 여러 호출 규약이 나타날 수 있으며, 성능, 다른 언어와의 호환성, 컴퓨팅 플랫폼 제약 등 다양한 이유로 선택된다.

많은 RISC 아키텍처(SPARC, MIPS, RISC-V 등)는 하나의 널리 사용되는 호출 규약을 가지며, 레지스터 이름도 이에 기반한다. 예를 들어, MIPS 레지스터 $4~$7는 ABI 이름 $a0~$a3를 가지며, 이는 매개변수 전달에 사용됨을 나타낸다.

프로그램의 언어 호출 규약은 기본 플랫폼, OS, 또는 라이브러리의 호출 규약과 다를 수 있다. 예를 들어, 32비트 Windows에서 OS 호출은 ''stdcall''을 사용하는 반면, 많은 C 프로그램은 ''cdecl''을 사용한다. 이러한 차이를 수용하기 위해 컴파일러는 함수 호출 규약을 지정하는 키워드를 허용하기도 한다.

일부 언어는 함수의 호출 규약을 명시적으로 지정할 수 있게 하지만, 다른 언어는 이를 숨겨 프로그래머가 고려하지 않아도 되게 한다.

각 플랫폼별 주요 호출 규약은 다음과 같다.

  • x86: 32비트 x86 아키텍처는 다양한 호출 규약을 사용하며, 주로 스택을 통해 인수를 전달한다.
  • x86-64: 64비트 x86 아키텍처는 윈도우용 Microsoft x64 호출 규약과 유닉스 계열 시스템용 System V AMD64 ABI 호출 규약을 주로 사용한다.
  • ARM: ARM 아키텍처는 32비트(A32)와 64비트(A64) 호출 규약이 있으며, 레지스터 할당 방식에 차이가 있다.
  • 기타 아키텍처: 파워PC, MIPS, SPARC, IBM System/360, System/390, z/Architecture, SuperH, 모토로라 68000 시리즈, IBM 1130 등 다양한 아키텍처별 호출 규약이 존재한다.

2. 1. x86

x86 아키텍처의 32비트 버전은 다양한 호출 규약과 함께 사용된다. 적은 수의 아키텍처 레지스터, 단순성, 작은 코드 크기에 대한 역사적 초점을 고려하여, 많은 x86 호출 규약은 스택에서 인수를 전달한다. 반환 값 (또는 이에 대한 포인터)은 레지스터에서 반환된다. 일부 규약은 처음 몇 개의 매개변수에 레지스터를 사용하여 성능을 향상시키는데, 특히 짧고 간단하며 매우 자주 호출되는 리프 루틴(다른 루틴을 호출하지 않는 루틴)에 유용하다.

일반적인 x86 호출 예시는 다음과 같다 (FASM/TASM 구문).

```nasm

push EAX ; 일부 레지스터 결과 전달

push dword [EBP+20] ; 일부 메모리 변수 전달

push 3 ; 일부 상수 전달

call calc ; 반환된 결과는 이제 EAX에 있음

```

일반적인 호출 대상 구조는 다음과 같다 (아래 명령어 중 일부 또는 전부(ret 제외)는 간단한 프로시저에서 최적화될 수 있다).

```nasm

calc:

push EBP ; 이전 프레임 포인터 저장

mov EBP,ESP ; 새 프레임 포인터 가져오기

sub ESP,localsize ; 로컬용 스택 공간 예약

.

. ; 계산 수행, EAX에 결과 남김

.

mov ESP,EBP ; 로컬용 공간 해제

pop EBP ; 이전 프레임 포인터 복원

ret paramsize ; 매개변수 공간 해제 및 반환.

```

x86 아키텍처에는 cdecl, stdcall, fastcall, thiscall 등 다양한 호출 규약이 존재한다. 각 호출 규약에 대한 자세한 설명은 하위 섹션을 참고하면 된다.

2. 1. 1. cdecl

cdecl은 인텔(Intel) x86 기반 시스템의 C/C++(C++)에서 많이 사용되는 호출 규약이다. 함수 인수는 오른쪽에서 왼쪽 순서로 스택에 쌓이며, 함수 반환값은 EAX 레지스터에 저장된다. EAX, ECX, EDX 레지스터는 호출된 함수에서 자유롭게 사용할 수 있으며, 호출 측에서는 필요에 따라 호출 전에 해당 레지스터를 저장해야 한다. 스택 포인터 처리는 호출 측에서 수행한다.

예를 들어, 다음과 같은 C 함수 호출은:

```c

int function(int, int, int);

int a, b, c, x;

...

x = function(a, b, c);

```

다음과 같은 MASM x86 어셈블리 언어 코드를 생성한다.

```asm

push c

push b

push a

call function

add esp, 12 ; 스택 상의 인수를 제거

mov x, eax

```

C 소스 코드에서 `a`, `b`, `c` 순서로 기술된 인수는 역순서 `c`, `b`, `a`로 스택에 쌓인다. `call` 명령은 반환 주소를 스택에 쌓은 다음 서브루틴으로 점프한다. 반환 후 호출 측에서는 스택 상의 인수 데이터를 제거한다.

cdecl은 일반적으로 x86 C 컴파일러의 기본값이며, 델파이(Delphi)를 포함한 다른 컴파일러에서도 cdecl 호출 규약으로 변경하는 옵션을 제공한다. 수동으로 지정하려면 다음과 같이 한다.

```c

void _cdecl function(params);

```

`_cdecl`은 프로토타입 선언부 또는 함수 선언부에 작성해야 한다.

OS/2상의 Virtual Pascal에서는 다음과 같이 `CDECL` 지령을 붙인다.

```pascal

function MyWndProc(h : HWND; m : ULONG; mp1 : MPARAM; mp2 : MPARAM) : MRESULT; CDECL;

```

이 컴파일러는 일반적으로 파스칼 호출 규약을 사용하지만, 이 함수는 OS/2 윈도우 시스템(Presentation Manager)에서 호출되므로 시스템 측에 맞춰 cdecl 호출 규약으로 변경해야 한다.

2. 1. 2. stdcall

윈도우 API에서 사용되는 호출 규약이다. 인수는 오른쪽에서 왼쪽으로 스택에 전달된다. EAX, ECX, EDX 레지스터는 호출된 함수에서 값을 보존하지 않고 자유롭게 사용할 수 있다. 반환 값은 EAX 레지스터에 저장된다. cdecl과 달리, 스택 정리는 호출된 함수(피호출자)가 수행한다. 이는 파스칼 호출 규약과 동일하다. 단, 인수 목록이 가변 길이인 경우에는 호출자가 스택 정리를 수행한다.

2. 1. 3. fastcall

x86 아키텍처의 32비트 버전에서 사용되는 호출 규약 중 하나로, 일부 인수를 레지스터에 전달하여 성능 향상을 꾀한다. 특히 짧고 간단하며 자주 호출되는, 다른 루틴을 호출하지 않는 리프 루틴에 유용하다.[25]

역사적인 사정으로 컴파일러에 따라 레지스터 전달 방식이 다르지만, 일반적으로 프로세서의 레지스터 비트 폭과 개수에 맞춰 처음 몇 개의 인수를 레지스터에 저장하여 서브루틴에 전달한다. 나머지 인수는 cdecl과 마찬가지로 오른쪽에서 왼쪽 순서로 스택에 쌓는다. 반환 값은 AL, AX, EAX 레지스터에 저장한다. 함수의 인수 개수가 가변적이지 않은 경우, 스택에서 인수 데이터를 제거하는 것은 서브루틴 측에서 수행하는 것이 일반적이다.[25]

메모리보다 레지스터가 읽고 쓰기가 빠르기 때문에 레지스터 전달은 효율이 높다.[25]

몇몇 컴파일러의 레지스터 전달 방식은 다음과 같다.

컴파일러레지스터 전달 방식
마이크로소프트 __fastcall (별명 __msfastcall)처음 2개의 인수를 ECX 및 EDX에 저장
보랜드 __fastcall처음 3개의 인수를 EAX, EDX, ECX에 저장
Watcom __fastcall처음 4개의 인수를 EAX, EDX, EBX, ECX에 저장



Watcom C/C++ 컴파일러에서는 #pragma aux 컴파일러 지시어를 사용하여 프로그래머가 자체 호출 규약을 지정할 수 있다.

2. 1. 4. thiscall

C++ 멤버 함수 호출에 사용되는 규약이다. GCC에서 `thiscall`은 대부분 cdecl과 유사하게 작동한다. 호출 측에서 스택을 정리하고, 인자는 오른쪽에서 왼쪽 순으로 전달된다. 주요 차이점은 모든 인자를 전달한 후 마지막으로 `this` 포인터가 스택에 쌓인다는 것이다.

Windows의 경우, 함수가 가변 인자를 취하지 않는다면, 인자는 오른쪽에서 왼쪽으로 전달되며, `this` 포인터는 ECX 레지스터에 저장된다. 호출된 함수 측에서 스택을 정리한다.

`thiscall`은 키워드가 아니기 때문에 명시적으로 지정할 수 없지만, IDA와 같은 디스어셈블러에서는 이를 지정해야 한다. 이를 위해 `__thiscall__`이라는 키워드가 제공된다.

2. 2. x86-64

x86-64는 x86 아키텍처의 64비트 버전으로, AMD64 및 인텔 64라고도 불린다. x86-64는 16비트 x86보다 더 많은 범용 레지스터를 가지고 있어, 이를 활용하여 호출 규약을 개선했다. x86-64에서는 일반적으로 두 가지 주요 호출 규약이 사용된다.

  • Microsoft x64 호출 규약: 윈도우에서 사용된다.
  • System V AMD64 ABI 호출 규약: 유닉스 계열 시스템(예: 리눅스, BSD, macOS)과 OpenVMS에서 사용된다.


두 규약 모두 일부 인수를 레지스터로 전달하여 함수 호출의 효율성을 높였다.[1]

x86-64 호출 규약 비교
구분Microsoft x64System V AMD64 ABI
정수 및 포인터 인자RCX, RDX, R8, R9RDI, RSI, RDX, RCX, R8, R9
부동 소수점 인자XMM0, XMM1, XMM2, XMM3XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7
반환 값RAX 또는 XMM0RAX
시스템 콜(별도 명시 없음)RCX 대신 R10 사용
기타레지스터 부족 시 스택 사용, 호출 측에서 스택 포인터 처리레지스터 부족 시 스택 사용


2. 2. 1. Microsoft x64

x64 호출 규약은 x86-64(AMD64) / EM64T에서 추가된 레지스터 공간을 활용한다. RCX, RDX, R8, R9 레지스터는 정수형과 포인터형 인수에, XMM0, XMM1, XMM2, XMM3은 부동 소수점형 인수에 사용된다. 나머지 인수는 스택에 놓인다. __m128형은 XMM 레지스터를 사용하지 않고 스택에 놓이며 정수형으로 포인터 전달된다. 호출된 측의 함수에서는 RAX, RCX, RDX, R8, R9, R10, R11, XMM0:XMM5 레지스터의 원래 값을 저장하지 않고 사용해도 좋다. 호출하는 측의 함수에서는 필요하다면 호출하기 전에 해당 레지스터를 스택 등에 저장한다. 레지스터가 부족해지면 스택이 사용된다. 반환 값은 RAX 또는 XMM0에 저장된다. 스택 포인터 처리는 호출 측에서 수행한다.

2. 2. 2. System V AMD64 ABI

x86-64 아키텍처에서 사용되는 호출 규약 중 하나로, 리눅스, BSD, macOS 등 유닉스 계열 운영 체제에서 사용된다.

구분내용
정수 및 포인터 인자RDI, RSI, RDX, RCX, R8, R9 레지스터
부동 소수점 인자XMM0, XMM1, XMM2, XMM3, XMM4, XMM5, XMM6, XMM7 레지스터
반환 값RAX 레지스터
시스템 콜RCX 대신 R10 레지스터 사용
기타레지스터만으로 인자 수가 부족할 경우 스택 사용


2. 2. 3. vectorcall

SIMD(Single Instruction, Multiple Data) 연산을 위해 부동 소수점 및 벡터 인수를 효율적으로 전달하는 호출 규약이다.

부동 소수점 형식, __m128 형식, 그리고 이들과 동일한 형식을 최대 4개 멤버로 갖는 복합 형식은 XMM0:XMM5에, __m256 형식 및 그 복합 형식은 YMM0:YMM5를 사용한다. 수용되지 않은 부분은 정수 형식으로 포인터 전달된다. 반환 값은 EAX, EDX:EAX, XMM0:XMM3, YMM0:YMM3 중 하나에 저장된다.

그 외에는 '''마이크로소프트 fastcall'''을 준용하며, '''마이크로소프트 x64 호출 규약'''을 따른다.

2. 3. ARM

ARM 아키텍처모바일 기기 및 임베디드 시스템 개발에 널리 사용되므로, 관련 호출 규약을 이해하는 것이 중요하다. ARM 아키텍처에는 32비트 호출 규약(A32)과 64비트 호출 규약(A64)이 있다.

A32 호출 규약에서는 16개의 범용 레지스터가 사용되며, r0~r3는 함수 인자와 반환 값에, r4~r11은 지역 변수에 사용된다. A64 호출 규약에서는 31개의 범용 레지스터가 사용되며, x0~x7은 함수 인자와 반환 값에, x9~x15는 지역 변수에 사용된다. 또한, 부동 소수점 연산을 위한 레지스터도 별도로 할당된다.

2. 3. 1. A32 (ARM)

표준 32비트 ARM 호출 규약은 16개의 범용 레지스터를 다음과 같이 할당한다.

  • r15: 프로그램 카운터 (명령 집합 사양에 따름).
  • r14: 링크 레지스터. 서브루틴 호출에 사용되는 BL 명령은 반환 주소를 이 레지스터에 저장한다.
  • r13: 스택 포인터. "Thumb" 작동 모드의 Push/Pop 명령은 이 레지스터만 사용한다.
  • r12: 절차 내 호출 임시 레지스터.
  • r4 ~ r11: 지역 변수.
  • r0 ~ r3: 서브루틴에 전달된 인수 값과 서브루틴에서 반환된 결과.


반환된 값의 유형이 r0에서 r3에 맞추기에는 너무 크거나 컴파일 시간에 정적으로 크기를 결정할 수 없는 경우, 호출자는 실행 시간에 해당 값에 대한 공간을 할당하고 r0에 해당 공간에 대한 포인터를 전달해야 한다.

서브루틴은 r4에서 r11의 내용과 스택 포인터를 보존해야 한다 (예: 함수 프롤로그에서 스택에 저장한 다음 임시 공간으로 사용한 다음 함수 에필로그에서 스택에서 복원). 특히 다른 서브루틴을 호출하는 서브루틴은 해당 다른 서브루틴을 호출하기 전에 링크 레지스터 r14에 있는 반환 주소를 스택에 저장해야 한다. 그러나 이러한 서브루틴은 해당 값을 r14로 반환할 필요가 없다. 단순히 반환하기 위해 해당 값을 프로그램 카운터 r15에 로드해야 한다.

ARM 호출 규약은 전체 하강 스택을 사용하도록 규정한다. 또한 스택 포인터는 항상 4바이트 정렬되어야 하며 공용 인터페이스가 있는 함수 호출 시 항상 8바이트 정렬되어야 한다.[3]

이 호출 규약은 "일반적인" ARM 서브루틴이 다음을 수행하도록 한다.

  • 프롤로그에서 r4에서 r11을 스택에 푸시하고 r14의 반환 주소를 스택에 푸시한다 (이는 단일 STM 명령으로 수행할 수 있음).
  • 전달된 모든 인수 (r0 ~ r3)를 지역 임시 레지스터 (r4 ~ r11)에 복사한다.
  • 나머지 지역 임시 레지스터 (r4 ~ r11)에 다른 지역 변수를 할당한다.
  • BL을 사용하여 필요에 따라 계산을 수행하고 다른 서브루틴을 호출하며 r0 ~ r3, r12 및 r14가 보존되지 않는다고 가정한다.
  • 결과를 r0에 넣는다.
  • 에필로그에서 r4에서 r11을 스택에서 풀고 반환 주소를 프로그램 카운터 r15로 풀린다. 이는 단일 LDM 명령으로 수행할 수 있다.

2. 3. 2. A64 (ARM64)

64비트 ARM(AArch64) 호출 규약은 31개의 범용 레지스터를 다음과 같이 할당한다.[4]

  • x31 (SP): 스택 포인터 또는 문맥에 따라 제로 레지스터.
  • x30 (LR): 프로시저 링크 레지스터, 서브루틴에서 반환하는 데 사용.
  • x29 (FP): 프레임 포인터.
  • x19 ~ x28: 피호출자 저장.
  • x18 (PR): 플랫폼 레지스터. 일부 운영 체제 관련 특수 목적 또는 추가 호출자 저장 레지스터에 사용.
  • x16 (IP0) 및 x17 (IP1): 프로시저 간 호출 스크래치 레지스터.
  • x9 ~ x15: 지역 변수, 호출자 저장.
  • x8 (XR): 간접 반환 값 주소.
  • x0 ~ x7: 서브루틴으로 전달되는 인자 값과 서브루틴에서 반환되는 결과.


'x'로 시작하는 모든 레지스터는 'w'가 앞에 붙은 해당 32비트 레지스터를 갖는다. 따라서 32비트 x0는 w0라고 한다.

마찬가지로 32개의 부동 소수점 레지스터는 다음과 같이 할당된다.[5]

  • v0 ~ v7: 서브루틴으로 전달되는 인자 값과 서브루틴에서 반환되는 결과.
  • v8 ~ v15: 피호출자 저장, 그러나 하위 64비트만 보존해야 함.
  • v16 ~ v31: 지역 변수, 호출자 저장.

2. 4. 기타 아키텍처

파워PC 아키텍처는 레지스터가 많아 대부분의 함수가 단일 레벨 호출에서 모든 인수를 레지스터로 전달할 수 있다. 추가 인수는 스택으로 전달되며, 레지스터 기반 인수를 위한 공간도 스택에 할당된다. 이는 다중 레벨 호출(재귀 등)이 사용되거나 레지스터를 저장해야 하는 경우에 대비하기 위함이다. 가변 인수 함수에서도 유용하다. 분기 및 연결 명령은 반환 주소를 범용 레지스터와 별도로 특수 링크 레지스터에 저장한다.

32비트 MIPS를 위한 가장 일반적인 호출 규약은 O32 ABI이다.[7][8] 처음 네 개의 인수는 레지스터 $a0-$a3에 전달되고, 나머지 인수는 스택에 전달된다. 반환값은 레지스터 $v0에, 두 번째 반환값은 $v1에 저장된다. 64비트 ABI는 더 많은 인수를 레지스터에 허용하여 함수 호출 효율을 높인다.

SPARC 아키텍처는 레지스터 윈도를 기반으로 한다. 각 레지스터 윈도에는 24개의 접근 가능한 레지스터가 있다. 8개는 "in" 레지스터(%i0-%i7), 8개는 "local" 레지스터(%l0-%l7), 8개는 "out" 레지스터(%o0-%o7)이다. "in" 레지스터는 함수에 인수를 전달하는 데 사용되며, 추가 인수는 스택에 푸시된다. 함수 호출 시 "out" 레지스터는 "in" 레지스터가 된다. 대부분의 최신 유닉스 계열 시스템에서 따르는 System V ABI는,[12] 처음 6개의 인수를 "in" 레지스터 %i0부터 %i5까지 전달하고, %i6은 프레임 포인터, %i7은 반환 주소로 예약한다.

IBM System/360은 하드웨어 스택이 없는 아키텍처이다. OS/360 and successors에서 사용된 호출 규약은 다음과 같다. 호출 프로그램은 인수 주소 목록의 주소를 레지스터 1에 전달하고, 서브루틴 주소를 레지스터 15에 로드한 후 BALR 14,15 명령을 사용하여 호출된 루틴으로 분기한다.[19] 마지막 인수 주소에는 목록의 끝을 나타내는 최상위 비트가 설정된다. 호출된 루틴은 일반적으로 레지스터 13이 가리키는 저장 영역에 레지스터들을 저장한다. 반환 시에는 호출자의 레지스터를 복원하고, 레지스터 15에 반환 값을 전달한다.

System/390 ABI[13] 및 Linux에서 사용되는 z/Architecture ABI[14]에서는 레지스터 0과 1은 휘발성, 레지스터 2, 3, 4, 5는 매개변수 전달 및 반환 값에 사용된다. 레지스터 6은 매개변수 전달에 사용되며 호출자가 저장 및 복원해야 한다. 레지스터 7~13은 호출자가 사용할 수 있다. 레지스터 14는 반환 주소, 레지스터 15는 스택 포인터로 사용된다. 부동 소수점 레지스터 0과 2는 매개변수 전달 및 반환 값에, 부동 소수점 레지스터 4와 6은 호출자가 사용할 수 있으며 저장 및 복원해야 한다.

SuperH의 호출 규약은 운영 체제 및 컴파일러에 따라 다르다. 다음은 일반적인 규칙을 나타내는 표이다.

레지스터윈도우 CE 5.0gcc르네사스
R0반환 값.반환 값, 호출자 저장변수/임시 (보장되지 않음)
R1..R3임시 레지스터.호출자가 저장하는 스크래치 레지스터.변수/임시 (보장되지 않음)
R4..R7정수 인수의 처음 4개 워드.매개변수 전달, 호출자 저장인수 (보장되지 않음)
R8..R13영구 레지스터 (보존됨).피호출자 저장변수/임시 (보장됨)
R14기본 프레임 포인터 (보존됨).프레임 포인터, FP, 피호출자 저장변수/임시 (보장됨)
R15스택 포인터 (보존됨).스택 포인터, SP, 피호출자 저장스택 포인터 (보장됨)



모토로라 68000 시리즈에서 가장 흔한 호출 규약은 다음과 같다.[15][16][17][18] d0, d1, a0 및 a1은 임시 레지스터이고, 다른 모든 레지스터는 호출자 보존 레지스터이다. a6는 프레임 포인터이며, 컴파일러 옵션으로 비활성화할 수 있다. 매개변수는 스택에 오른쪽에서 왼쪽으로 푸시되고, 반환 값은 d0에 저장된다.

IBM 1130은 16비트 워드 어드레싱 소형 머신이었다. 6개의 레지스터와 조건 표시기만 있었고 스택은 없었다. 호출하는 프로그램은 ACC, EXT, X1, X2를 저장해야 한다.[19] 의사 연산자 `CALL`과 `LIBF`는 각각 비이동 가능 서브루틴과 이동 가능 라이브러리 서브루틴을 호출한다.[20] 이들은 유효 주소(EA)에 다음 명령어 주소를 저장하고 EA+1로 분기하는 `BSI` 머신 명령어로 변환된다. 인수는 `BSI`를 따르거나 레지스터로 전달될 수 있다. 함수 루틴은 실수 인수의 경우 ACC로, 실수 의사 누산기(FAC)라고 하는 메모리 위치로 결과를 반환했다.

3. 호출 규약과 언어 사양

호출 규약은 응용 프로그램 이진 인터페이스(ABI)의 일부로, 호출자(caller)와 호출되는 함수 간의 "계약"으로 볼 수 있다.[1] 응용 프로그래밍 인터페이스(API)는 매개변수와 반환 값의 이름, 의미 등을 정의하며, ABI 및 호출 규약과는 별개이다.

호출 규약은 일반적으로 동적으로 할당된 구조체와 객체의 생명 주기 처리, 바이트 순서, 구조체 패킹, 오류 및 예외 처리 (Go, Java는 예외) 등에 대한 세부 정보를 포함하지 않는다. 원격 프로시저 호출에서는 마샬링이라는 유사한 개념이 사용된다.

호출 규약은 특정 프로그래밍 언어의 평가 전략과 관련이 있을 수 있지만, 평가 전략은 더 추상적인 수준에서 정의되므로 대부분 호출 규약의 일부로 간주되지 않는다. 평가 전략은 컴파일러의 낮은 수준 구현 세부 사항으로 간주된다.

PL/I 언어의 기본 호출 규약은 모든 인수를 참조에 의해 전달하지만, 다른 규약도 선택 가능하다. 인수는 컴파일러 및 플랫폼에 따라 다르게 처리되지만, 일반적으로 인수 주소는 메모리의 인수 목록을 통해 전달된다. 반환 값을 포함하는 영역을 가리키는 숨겨진 주소가 전달될 수도 있다. 데이터 디스크립터가 전달되어 문자열, 비트 문자열의 길이, 배열의 차원 및 경계(도프 벡터), 데이터 구조의 레이아웃 및 내용을 정의할 수도 있다. '더미 인수'는 상수이거나 호출된 프로시저가 예상하는 인수 유형과 일치하지 않는 인수에 대해 생성된다.

언어 사양과 호출 규약은 독립적이지만, 호출 규약이 언어 사양에 영향을 미치는 경우가 있다. 예를 들어, `foobar(foo, bar)`와 같이 여러 인수를 받는 함수 호출에서, 파스칼 호출 규약은 인수를 왼쪽에서 오른쪽으로 저장하므로 `foo`를 먼저, `bar`를 나중에 평가하는 것이 효율적이다. cdecl에서는 반대로 `bar`를 먼저, `foo`를 나중에 평가하는 것이 자연스럽다. 이러한 호출 규약에 따라 컴파일러를 구현하면, 언어 사양을 구현 수준에서 결정하게 된다.

다음 파스칼 코드를 보자:

```pascal

var i : Integer;

...

function foo : Boolean;

begin

foo := i > 0;

i := 1

end;

function bar : Boolean;

begin

bar := i > 0;

i := -1

end;

function foobar(f, b : Boolean) : Boolean;

begin

foobar := f < b

end;

begin

i := 1;

write(foobar(foo, bar))

end;

```

`foobar(foo, bar)` 호출 시 인수 `foo`와 `bar` 중 어느 것을 먼저 평가하느냐에 따라 결과가 달라진다. `foo`를 먼저 평가하면 `foo`와 `bar`는 모두 참이 되므로 `foobar(foo, bar)`는 거짓이 된다. `bar`를 먼저 평가하면 `bar`는 참이지만 `foo`는 거짓이 되어 `foobar(foo, bar)`는 참이 된다. 표준 파스칼에서는 인수 평가 순서를 규정하지 않으며, 결과가 인수 평가 순서에 의존하는 프로그램은 좋지 않은 예로 간주된다.

4. 기타 호출 규약

safecall영어, clrcall영어, 파스칼(Pascal), 레지스터(register) 등은 덜 일반적이지만 특수한 상황에서 사용되는 호출 규약이다.


  • '''파스칼 호출 규약'''은 cdecl과 반대로 인수가 왼쪽에서 오른쪽 순서로 스택에 쌓이며, 스택상의 인수 데이터를 제거하는 것은 호출된 측(서브루틴 측)이다.[25] C와 달리, 인수의 개수와 형식이 서브루틴 선언 시점에서 완전히 고정되어 있기 때문에, 스택상의 인수 데이터의 바이트 수는 서브루틴 내부에서 판명된다. 리사 및 초기 매킨토시 시스템은 파스칼로 기술되었기 때문에, 이전 매킨토시 툴박스를 이용하기 위해서는 파스칼 호출 규약을 사용할 필요가 있었다.

  • '''레지스터 호출 규약'''(또는 fastcall영어)은 프로세서의 레지스터 비트 폭과 개수에 맞춰 처음 몇 개의 인수를 (스택이 아닌) 레지스터에 저장하여 서브루틴에 전달한다.[25] 나머지 인수는 cdecl과 마찬가지로 오른쪽에서 왼쪽 순서로 스택에 쌓인다. 메모리보다 레지스터가 읽고 쓰기가 빠르기 때문에, 레지스터 전달은 효율이 높다.


레지스터 전달의 예시는 다음과 같다.

호출 규약설명
마이크로소프트 __fastcall처음 2개의 인수를 ECX 및 EDX에 저장한다.
보랜드 __fastcall처음 3개의 인수를 EAX, EDX, ECX에 저장한다.
Watcom __fastcall처음 4개의 인수를 EAX, EDX, EBX, ECX에 저장한다.


  • '''stdcall영어'''은 윈도우 API에서 사용된다. cdecl과 달리, 스택 정리는 서브루틴 측에서 수행한다.

  • '''safecall영어'''은 윈도우에서 COM의 오류 처리를 캡슐화하는 데 사용된다.

  • '''thiscall영어'''은 C++의 멤버 함수를 호출하는 데 사용된다. GCC에서 thiscall영어은 대부분 cdecl과 유사하며, 호출 측에서 스택을 정리하고 인자는 오른쪽에서 왼쪽 순으로 전달된다.

5. 표준 서브루틴 프롤로그/에필로그

스레드 코드는 함수 호출을 설정하고 정리하는 모든 책임을 호출되는 코드에 맡긴다. 호출 코드는 호출할 서브루틴을 나열하는 것 외에는 아무것도 하지 않는다. 이렇게 하면 모든 함수 설정 및 정리 코드가 함수가 호출되는 여러 곳이 아닌 한 곳, 즉 함수의 프롤로그와 에필로그에 배치된다. 이로 인해 스레드 코드는 가장 간결한 호출 규칙이 된다.[22][23][24]

인텔x86 기반 시스템의 C/C++(C++)에서는 cdecl 호출 규약이 많이 사용된다. cdecl에서는 함수에 대한 인수는 오른쪽에서 왼쪽 순서로 스택에 쌓인다. 함수의 반환값은 EAX(x86 레지스터 중 하나)에 저장된다. 호출된 측의 함수에서는 EAX, ECX, EDX 레지스터의 원래 값을 보존하지 않고 사용할 수 있다. 호출 측의 함수에서는 필요하다면 호출 전에 해당 레지스터를 스택 등에 저장한다. 스택 포인터 처리는 호출 측에서 수행한다.

서브루틴으로 제어가 넘어갈 때, 표준적으로 다음과 같은 처리를 수행한다.

```asm

_function:

push ebp ; 베이스 포인터를 저장

mov ebp, esp ; 현재 스택 프레임을 가리키도록 베이스 포인터 변경

sub esp, x ; 지역 변수(C에서의 자동 변수) 크기만큼 스택 포인터를 감소

```

이 결과, EBP는 스택 상의 인수의 시작 부분을 가리키고, 지역 변수를 저장하는 영역을 스택에 확보할 수 있다. 원래 EBP 값은 스택에 저장된다. 이처럼 지역 변수는 인수와 마찬가지로 서브루틴 호출마다 스택에 확보되므로, 재귀 호출이 가능해진다.

이 서브루틴에서 빠져나갈 때는 다음 시퀀스를 실행한다.

```asm

mov esp, ebp ; 지역 변수 제거

pop ebp ; 베이스 포인터 복원

ret ; 서브루틴에서 반환

```

이는 cdecl의 예이며, 파스칼 호출 규약에서는 인수 데이터를 서브루틴 측에서 정리한다.

```asm

ret n ; n은 인수 데이터의 바이트 수

참조

[1] 웹사이트 Calling Conventions https://www.cs.corne[...] 2024-03-05
[2] 웹사이트 /Oy (Frame-Pointer Omission) https://learn.micros[...] 2021-08-03
[3] 웹사이트 Procedure Call Standard for the ARM Architecture https://github.com/A[...] 2021
[4] 웹사이트 Parameters in general-purpose registers https://developer.ar[...] 2020-11-12
[5] 웹사이트 Parameters in NEON and floating-point registers https://developer.ar[...] 2020-11-13
[6] 웹사이트 RISC-V calling convention https://riscv.org/wp[...]
[7] 웹사이트 MIPS32 Instruction Set Quick Reference https://www.mips.com[...]
[8] 서적 See MIPS Run Morgan Kaufmann Publishers
[9] 웹사이트 MIPS ABI History https://www.linux-mi[...]
[10] 간행물 mips eabi documentation https://sourceware.o[...] 2020-06-19
[11] 웹사이트 NUBI https://www.linux-mi[...]
[12] 서적 System V Application Binary Interface SPARC Processor Supplement http://sparc.org/wp-[...]
[13] 웹사이트 S/390 ELF Application Binary Interface Supplement http://refspecs.linu[...]
[14] 웹사이트 zSeries ELF Application Binary Interface Supplement https://refspecs.lin[...]
[15] 웹사이트 SHARC (21k) and 68k Register Comparison http://people.ucalga[...]
[16] 서적 XGCC: The Gnu C/C++ Language System for Embedded Development http://gendev.sprite[...] Embedded Support Tools Corporation
[17] 웹사이트 COLDFIRE/68K: ThreadX for the Freescale ColdFire Family http://rtos.com/prod[...]
[18] 웹사이트 Subroutines Continued: Passing Arguments, Returning Values and Allocating Local Variables http://www.eecg.toro[...]
[19] 서적 IBM 1130 Disk Monitor System, Version 2 System Introduction (C26-3709-0) http://media.ibm1130[...] 2014-12-21
[20] 서적 IBM 1130 Assembler Language (C26-5927-4) http://media.ibm1130[...] 1968
[21] 웹사이트 Subroutine and procedure call support: Early history http://people.cs.cle[...]
[22] 웹사이트 Moving Forth, Part 1: Design Decisions in the Forth Kernel http://www.bradrodri[...]
[23] 웹사이트 Speed of various interpreter dispatch techniques http://www.complang.[...]
[24] 웹사이트 Chapter 4: Design and Implementation of Efficient Interpretation http://www.cs.toront[...]
[25] 웹사이트 변수수식자 - RAD Studio XE2 http://docwiki.embar[...] Embarcadero Technologies, Inc. 2013-01-22



본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.

문의하기 : help@durumis.com