상위 질문
타임라인
채팅
관점
호출 규약
위키백과, 무료 백과사전
Remove ads
호출 규약(영어: calling convention)은 컴퓨터 과학에서 서브루틴이나 함수가 호출자로부터 매개변수를 받고 결과를 반환하는 방법에 대한 구현 수준(저수준)의 방식이다.[1] 어떤 코드가 함수를 호출할 때, 매개변수를 해당 함수로 전달하는 방법과 위치, 그리고 결과를 해당 함수에서 반환하는 방법과 위치에 대한 설계 결정이 이루어지며, 이러한 전송은 일반적으로 특정 레지스터를 통해 또는 콜 스택의 스택 프레임 내에서 수행된다. 함수 호출을 준비하고 함수 완료 후 환경을 복원하는 작업을 호출자와 피호출자 간에 어떻게 분할할지에 대한 설계 결정도 있다. 일부 호출 규약은 모든 함수가 호출되어야 하는 방식을 지정한다. 이러한 함수를 사용하는 전체 프로그램의 정확하고 신뢰할 수 있는 실행을 위해 모든 함수 호출에 올바른 호출 규약을 사용해야 한다.
Remove ads
서론
요약
관점
호출 규약은 일반적으로 응용 프로그램 이진 인터페이스 (ABI)의 일부로 간주된다. 이는 호출자와 호출되는 함수 간의 계약으로 간주될 수 있다.[1]
관련 개념
매개변수 및 반환 값의 이름 또는 의미는 응용 프로그램 프로그래밍 인터페이스 (API, ABI와는 대조적으로)에 정의되어 있으며, 이는 ABI 및 호출 규약과는 별개이지만 관련이 있는 개념이다. 전달된 구조체 및 객체 내의 멤버 이름도 API의 일부로 간주되며, ABI의 일부가 아니다. 때로는 API에 함수의 호출 규약을 지정하는 키워드가 포함되기도 한다.
호출 규약은 일반적으로 동적으로 할당된 구조체 및 객체의 수명 처리 정보를 포함하지 않는다. 다른 보조 문서는 할당된 메모리를 해제하는 책임이 어디에 있는지 명시할 수 있다.
호출 규약은 바이트 순서 또는 구조체 패킹과 같이 구조체 및 객체 내의 항목 레이아웃을 지정할 가능성이 낮다.
일부 언어의 경우 호출 규약에 오류 또는 예외 처리 세부 정보가 포함되지만(예: Go, 자바), 다른 언어의 경우 그렇지 않다(예: C++).
원격 프로시저 호출에는 마샬링이라는 유사한 개념이 있다.
호출 규약은 특정 프로그래밍 언어의 평가 전략과 관련될 수 있지만, 대부분의 경우 그 일부로 간주되지 않는다(또는 그 반대). 평가 전략은 일반적으로 더 높은 추상화 수준에서 정의되며 특정 언어의 컴파일러의 저수준 구현 세부 사항이라기보다는 언어의 일부로 간주되기 때문이다.
다른 호출 규약
호출 규약은 다음과 같은 점에서 다를 수 있다.
- 매개변수가 배치되는 위치. 옵션에는 레지스터, 콜 스택, 둘의 혼합 또는 다른 메모리 구조가 포함된다.
- 매개변수가 전달되는 순서. 옵션에는 왼쪽에서 오른쪽 순서, 오른쪽에서 왼쪽 순서 또는 더 복잡한 순서가 포함된다.
- 가변 개수의 인수를 받는 함수(가변 인자 함수)가 처리되는 방법. 옵션에는 단순히 순서대로 전달(첫 번째 매개변수가 명확한 위치에 있다고 가정)하거나 가변 부분을 배열에 담는 것이 포함된다.
- 반환 값이 피호출자에서 호출자로 전달되는 방법. 옵션에는 스택, 레지스터 또는 힙에 할당된 무언가에 대한 참조가 포함된다.
- 길거나 복잡한 값이 처리되는 방법, 아마도 여러 레지스터에 걸쳐 분할되거나, 스택 프레임 내에서, 또는 메모리 참조를 통해.
- 피호출자가 반환될 때 피호출자가 호출되었을 때와 동일한 값을 가질 것이 보장되는 레지스터. 이 레지스터는 저장되거나 보존되었다고 하며, 휘발성이 아니다.
- 함수 호출을 설정하고 정리하는 작업이 호출자와 피호출자 사이에 어떻게 분할되는지. 특히 피호출자가 완료된 후 호출자가 계속할 수 있도록 스택 프레임이 어떻게 복원되는지.
- 인수를 설명하는 메타데이터가 전달되는지 여부 및 방법
- 서브루틴이 끝날 때 스택 프레임을 복원하는 데 사용되는 프레임 포인터의 이전 값이 저장되는 위치. 옵션에는 콜 스택 내부 또는 특정 레지스터가 포함된다. 때로는 프레임 포인터가 전혀 사용되지 않기도 한다.[2]
- 루틴의 비지역 데이터 액세스에 대한 정적 범위 링크가 배치되는 위치(일반적으로 스택 프레임의 하나 이상의 위치에 있지만 때로는 일반 레지스터에 있거나 일부 아키텍처의 경우 특수 목적 레지스터에 있음)
- 객체 지향 언어의 경우 함수의 객체가 참조되는 방법
단일 플랫폼 내에서의 호출 규약
때때로 단일 플랫폼에 여러 호출 규약이 나타나기도 한다. 주어진 플랫폼 및 언어 구현은 호출 규약 선택을 제공할 수 있다. 이에 대한 이유는 성능, 다른 인기 언어의 규약 적응, 다양한 "컴퓨팅 플랫폼"에서 부과하는 제한 또는 규약 등이 있다.
많은 아키텍처는 아키텍트가 제안하는 단 하나의 널리 사용되는 호출 규약을 가지고 있다. SPARC, MIPS 및 RISC-V를 포함한 RISC의 경우 이 호출 규약을 기반으로 한 레지스터 이름이 자주 사용된다. 예를 들어, MIPS 레지스터 $4에서 $7까지는 "ABI 이름" $a0에서 $a3를 가지며, 이는 표준 호출 규약에서 매개변수 전달에 사용되는 것을 반영한다. (RISC CPU는 많은 동등한 범용 레지스터를 가지고 있으므로 일반적으로 숫자 외의 이름을 부여해야 할 하드웨어적인 이유는 없다.)
주어진 프로그램 언어의 호출 규약은 기본 플랫폼, OS 또는 링크되는 일부 라이브러리의 호출 규약과 다를 수 있다. 예를 들어, 32비트 윈도우에서는 운영 체제 호출에 stdcall 호출 규약이 있지만, 그곳에서 실행되는 많은 C 프로그램은 cdecl 호출 규약을 사용한다. 이러한 호출 규약의 차이를 수용하기 위해 컴파일러는 종종 주어진 함수의 호출 규약을 지정하는 키워드를 허용한다. 함수 선언에는 사용될 호출 규약을 나타내는 추가 플랫폼별 키워드가 포함된다. 올바르게 처리되면 컴파일러는 함수를 적절한 방식으로 호출하는 코드를 생성한다.
일부 언어는 함수의 호출 규약을 해당 함수로 명시적으로 지정할 수 있다. 다른 언어는 호출 규약을 가지지만 해당 언어 사용자에게는 숨겨져 있으므로 일반적으로 프로그래머가 고려할 사항이 아니다.
Remove ads
아키텍처
요약
관점
x86 (32비트)
X86 아키텍처의 32비트 버전은 다양한 호출 규약과 함께 사용된다. 레지스터의 수가 적고, 단순성과 작은 코드 크기에 대한 역사적 초점으로 인해 많은 x86 호출 규약은 스택을 통해 인수를 전달한다. 반환 값 (또는 그에 대한 포인터)은 레지스터로 반환된다. 일부 규약은 처음 몇 개의 매개변수에 레지스터를 사용하여 성능을 향상시킬 수 있으며, 특히 매우 자주 호출되는 짧고 간단한 리프 루틴(즉, 다른 루틴을 호출하지 않는 루틴)의 경우 더욱 그렇다.
호출 예시:
push EAX ; 일부 레지스터 결과 전달
push dword [EBP+20] ; 일부 메모리 변수 전달 (FASM/TASM 문법)
push 3 ; 일부 상수 전달
call calc ; 반환된 결과는 이제 EAX에 있음
일반적인 피호출자 구조: (아래 지시어 중 일부 또는 전부(ret 제외)는 간단한 프로시저에서 최적화될 수 있다.) 일부 규약은 매개변수 공간을 할당된 상태로 두고 ret imm16 대신 일반 ret을 사용한다. 이 경우 호출자는 이 예시에서 add esp,12를 사용하거나 ESP의 변경을 처리할 수 있다.
calc:
push EBP ; 이전 프레임 포인터 저장
mov EBP,ESP ; 새 프레임 포인터 가져오기
sub ESP,localsize ; 지역 변수용 스택 공간 예약
.
. ; 계산 수행, EAX에 결과 저장
.
mov ESP,EBP ; 지역 변수 공간 해제
pop EBP ; 이전 프레임 포인터 복원
ret paramsize ; 매개변수 공간 해제 및 반환.
x86-64
x86 아키텍처의 64비트 버전인 X86-64, AMD64, Intel 64는 일반적으로 두 가지 호출 시퀀스를 사용한다. Microsoft가 정의한 호출 시퀀스는 Windows에서 사용되고, AMD64 System V ABI에 명시된 다른 호출 시퀀스는 유닉스 계열 시스템과 일부 변경 사항과 함께 OpenVMS에서 사용된다. x86-64는 32비트 x86보다 더 많은 범용 레지스터를 가지고 있으므로, 두 가지 규약 모두 일부 인수를 레지스터로 전달한다.
ARM (A32)
표준 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)에 다른 지역 변수를 할당한다.
- r0 ~ r3, r12, r14가 보존되지 않는다고 가정하고 BL을 사용하여 필요에 따라 계산을 수행하고 다른 서브루틴을 호출한다.
- 결과를 r0에 저장한다.
- 에필로그에서 r4 ~ r11을 스택에서 가져오고, 반환 주소를 프로그램 카운터 r15로 가져온다. 이는 단일 LDM 명령어로 수행할 수 있다.
ARM (A64)
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: 지역 변수, 호출자 저장.
RISC-V ISA
RISC-V는 부동 소수점 유무에 따라 두 가지 방식의 호출 규약을 정의한다.[6] 가능한 경우 항상 레지스터로 인수를 전달한다.
POWER, PowerPC 및 Power ISA
POWER, 파워PC, Power ISA 아키텍처는 많은 수의 레지스터를 가지고 있어 대부분의 함수는 단일 레벨 호출에 대한 모든 인수를 레지스터로 전달할 수 있다. 추가 인수는 스택에 전달되며, 레지스터 기반 인수를 위한 공간도 멀티 레벨 호출(재귀적 또는 기타)이 사용되고 레지스터를 저장해야 하는 경우를 대비하여 호출된 함수에 대한 편의를 위해 항상 스택에 할당된다. 이는 printf()와 같은 가변 인자 함수에서도 유용하며, 이 경우 함수의 인수에 배열로 액세스해야 한다. 모든 절차적 언어에 단일 호출 규약이 사용된다.
분기 및 링크 명령어는 반환 주소를 범용 레지스터와 별개인 특수 링크 레지스터에 저장한다. 루틴은 링크 레지스터를 대상 주소로 사용하는 분기 명령어로 호출자에게 반환한다. 리프 루틴은 링크 레지스터를 저장하거나 복원할 필요가 없지만, 비 리프 루틴은 다른 루틴을 호출하기 전에 반환 주소를 저장하고 반환하기 전에 복원해야 한다. 이는 특수 목적 레지스터에서 이동 명령어를 사용하여 링크 레지스터를 범용 레지스터로 이동하고, 필요한 경우 스택에 저장하여 저장한다. 그리고 스택에 저장되었다면 저장된 링크 레지스터 값을 범용 레지스터로 로드한 다음, 특수 목적 레지스터로 이동 명령어를 사용하여 저장된 링크 레지스터 값을 포함하는 레지스터를 링크 레지스터로 이동하여 복원한다.
MIPS
O32[7] ABI는 MIPS의 원래 시스템 V ABI 지위로 인해 가장 일반적으로 사용되는 ABI이다.[8] 이는 엄격하게 스택 기반이며, 인수를 전달하는 데 사용할 수 있는 레지스터는 $a0-$a3 4개뿐이다. 이러한 느리다고 인식되는 속도와 16개 레지스터만 있는 구식 부동 소수점 모델은 다른 많은 호출 규약의 확산을 부추겼다. 이 ABI는 1990년에 형성되었으며 1994년 이후로 업데이트되지 않았다. 32비트 MIPS에 대해서만 정의되어 있지만, GCC는 O64라는 64비트 변형을 만들었다.[9]
64비트의 경우, 실리콘 그래픽스의 N64 ABI (닌텐도 64와는 관련 없음)가 가장 일반적으로 사용된다. 가장 중요한 개선 사항은 이제 8개의 레지스터를 인수 전달에 사용할 수 있다는 점이다. 또한 부동 소수점 레지스터의 수를 32개로 늘린다. 더 작은 코드를 위해 32비트 포인터를 사용하는 N32라는 ILP32 버전도 있으며, 이는 x32 ABI와 유사하다. 둘 다 CPU의 64비트 모드에서 실행된다.[9]
O32를 N32와 더 유사한 32비트 ABI로 대체하려는 몇 가지 시도가 있었다. 1995년 회의에서는 32비트 버전이 상당히 유사한 MIPS EABI가 나왔다.[10] EABI는 MIPS Technologies가 인수 레지스터를 반환 값에 재사용하는 더 급진적인 "NUBI" ABI를 제안하도록 영감을 주었다.[11] MIPS EABI는 GCC에서 지원되지만 LLVM에서는 지원되지 않는다. 둘 다 NUBI를 지원하지 않는다.
O32 및 N32/N64의 모든 경우에 반환 주소는 $ra 레지스터에 저장된다. 이는 JAL (점프 및 링크) 또는 JALR (점프 및 링크 레지스터) 명령어 사용 시 자동으로 설정된다. 스택은 아래쪽으로 자란다.
SPARC
SPARC 아키텍처는 대부분의 RISC 아키텍처와 달리 레지스터 윈도를 기반으로 한다. 각 레지스터 윈도에는 24개의 접근 가능한 레지스터가 있다. 이 중 8개는 "in" 레지스터(%i0-%i7), 8개는 "local" 레지스터(%l0-%l7), 8개는 "out" 레지스터(%o0-%o7)이다. "in" 레지스터는 호출되는 함수에 인수를 전달하는 데 사용되며, 추가 인수는 스택에 푸시되어야 한다. 그러나 호출된 함수는 레지스터 윈도 오버플로, 지역 변수, 그리고 (32비트 SPARC의 경우) 값에 의한 구조체 반환을 처리하기 위해 항상 공간을 할당한다. 함수를 호출하려면 호출될 함수에 대한 인수를 "out" 레지스터에 배치한다. 함수가 호출되면 "out" 레지스터는 "in" 레지스터가 되고 호출된 함수는 "in" 레지스터에 있는 인수에 접근한다. 호출된 함수가 완료되면 반환 값을 첫 번째 "in" 레지스터에 배치하며, 이는 호출된 함수가 반환될 때 첫 번째 "out" 레지스터가 된다.
대부분의 현대 유닉스 계열 시스템이 따르는 System V ABI[12]는 첫 번째 6개의 인수를 "in" 레지스터 %i0에서 %i5까지 전달하며, %i6은 프레임 포인터로, %i7은 반환 주소로 예약한다.
IBM System/360 및 후속 모델
IBM 시스템/360은 하드웨어 스택이 없는 또 다른 아키텍처이다. 아래 예시는 64비트 Z/아키텍처 도입 이전에 OS/360에서 사용된 호출 규약을 보여준다. System/360의 다른 운영 체제는 다른 호출 규약을 가질 수 있다.
호출 프로그램:
LA 1,ARGS 인수 목록 주소 로드
L 15,=A(SUB) 서브루틴 주소 로드
BALR 14,15 호출된 루틴으로 분기1
...
ARGS DC A(FIRST) 첫 번째 인수의 주소
DC A(SECOND)
...
DC A(THIRD)+X'80000000' 마지막 인수2
호출된 프로그램:
SUB EQU * 이것은 서브프로그램의 진입점이다.
표준 진입 시퀀스:
USING *,153
STM 14,12,12(13) 레지스터 저장4
ST 13,SAVE+4 호출자의 저장 영역 주소 저장
LA 12,SAVE 저장 영역 체인
ST 12,8(13)
LR 13,12
...
표준 반환 시퀀스:
L 13,SAVE+45
LM 14,12,12(13)
L 15,RETVAL6
BR 14 호출자로 반환
SAVE DS 18F 저장 영역7
주석:
BALR명령어는 다음 명령어의 주소(반환 주소)를 첫 번째 인수로 지정된 레지스터인 레지스터 14에 저장하고 두 번째 인수 주소인 레지스터 15로 분기한다.- 호출자는 레지스터 1에 인수 주소 목록의 주소를 전달한다. 마지막 주소는 목록의 끝을 나타내기 위해 상위 비트가 설정된다. 이로 인해 이 규약을 사용하는 프로그램은 31비트 주소 지정으로 제한된다.
- 호출된 루틴의 주소는 레지스터 15에 있다. 일반적으로 이는 다른 레지스터에 로드되며 레지스터 15는 기본 레지스터로 사용되지 않는다.
STM명령어는 레지스터 14, 15, 그리고 0부터 12까지의 레지스터를 호출자가 제공한 72바이트 영역에 저장하며, 이 영역은 레지스터 13이 가리키는 저장 영역이다. 호출된 루틴은 자신이 호출하는 서브루틴이 사용할 자신만의 저장 영역을 제공한다. 이 영역의 주소는 일반적으로 루틴 전체에서 레지스터 13에 유지된다.STM뒤의 명령어는 이 저장 영역을 호출자의 저장 영역과 연결하는 전방 및 후방 체인을 업데이트한다.- 반환 시퀀스는 호출자의 레지스터를 복원한다.
- 레지스터 15는 일반적으로 반환 값을 전달하는 데 사용된다.
- 호출된 루틴에서 `savearea`를 정적으로 선언하면 재진입성이 없고 재귀적이지 않다. 재진입 프로그램은 운영 체제로부터 획득하고 반환 시 해제되거나 호출 프로그램에서 전달된 저장 공간에 동적 `savearea`를 사용한다.
System/390 ABI[13] 및 Z/아키텍처 ABI[14](Linux에서 사용됨)에서는 다음과 같다.
- 레지스터 0과 1은 휘발성이다.
- 레지스터 2와 3은 매개변수 전달 및 반환 값에 사용된다.
- 레지스터 4와 5도 매개변수 전달에 사용된다.
- 레지스터 6은 매개변수 전달에 사용되며, 피호출자가 저장하고 복원해야 한다.
- 레지스터 7부터 13까지는 피호출자가 사용하며, 피호출자가 저장하고 복원해야 한다.
- 레지스터 14는 반환 주소에 사용된다.
- 레지스터 15는 스택 포인터로 사용된다.
- 부동 소수점 레지스터 0과 2는 매개변수 전달 및 반환 값에 사용된다.
- 부동 소수점 레지스터 4와 6은 피호출자가 사용하며, 피호출자가 저장하고 복원해야 한다.
- Z/아키텍처에서 부동 소수점 레지스터 1, 3, 5, 그리고 7부터 15까지는 피호출자가 사용한다.
- 액세스 레지스터 0은 시스템 사용을 위해 예약되어 있다.
- 액세스 레지스터 1부터 15까지는 피호출자가 사용한다.
추가 인수는 스택에 전달된다.
SuperH
참고: "preserved"는 피호출자 저장에 해당하며, "guaranteed"도 마찬가지이다.
68k
모토로라 68000 시리즈의 가장 일반적인 호출 규약은 다음과 같다.[15][16][17][18]
- d0, d1, a0 및 a1은 스크래치 레지스터이다.
- 다른 모든 레지스터는 피호출자 저장(callee-saved)이다.
- a6는 프레임 포인터이며, 컴파일러 옵션으로 비활성화할 수 있다.
- 매개변수는 오른쪽에서 왼쪽으로 스택에 푸시된다.
- 반환 값은 d0에 저장된다.
IBM 1130
IBM 1130은 작은 16비트 워드 주소 지정 가능 기계였다. 레지스터는 6개뿐이었고 조건 지시자가 있었으며 스택은 없었다. 레지스터는 명령어 주소 레지스터(IAR), 누산기(ACC), 누산기 확장(EXT), 그리고 세 개의 인덱스 레지스터 X1–X3이었다. 호출 프로그램은 ACC, EXT, X1, X2를 저장할 책임이 있었다.[19] 서브루틴 호출을 위한 두 가지 의사 연산이 있었다. 메인 프로그램과 직접 연결되는 비재배치 가능 서브루틴을 호출하는 CALL과 전송 벡터를 통해 재배치 가능 라이브러리 서브루틴을 호출하는 LIBF이다.[20] 두 의사 연산은 모두 Branch and Store IAR (BSI) 기계 명령어로 변환되며, 이 명령어는 다음 명령어의 주소를 유효 주소(EA)에 저장하고 EA+1로 분기한다.
인수는 BSI 뒤에 온다. 일반적으로 이는 인수의 한 워드 주소이다. 호출된 루틴은 반환 시 인수를 건너뛸 수 있도록 예상되는 인수 수를 알아야 한다. 또는 인수를 레지스터로 전달할 수도 있다. 함수 루틴은 실제 인수의 경우 ACC에, 또는 Real Number Pseudo-Accumulator (FAC)라고 하는 메모리 위치에 결과를 반환했다. 인수와 반환 주소는 서브루틴의 첫 번째 위치에 저장된 IAR 값에 대한 오프셋을 사용하여 주소 지정되었다.
* 1130 서브루틴 예시
ENT SUB "SUB"를 외부 진입점으로 선언
SUB DC 0 진입점의 예약어, 관례적으로 "DC *-*"로 코딩됨
* 서브루틴 코드는 여기에서 시작한다.
* 인수가 있었다면 반환 주소에서 간접적으로 주소를 로드할 수 있다.
LDX I 1 SUB X1에 첫 번째 인수의 주소 로드 (예시)
...
* 반환 시퀀스
LD RES ACC에 정수 결과 로드
* 인수가 제공되지 않았다면, 저장된 반환 주소로 간접 분기
B I SUB 인수가 제공되지 않았다면
END SUB
IBM 1130, CDC 6600, PDP-8(세 컴퓨터 모두 1965년에 출시됨)의 서브루틴은 반환 주소를 서브루틴의 첫 번째 위치에 저장한다.[21]
Remove ads
머신 아키텍처 외부의 호출 규약
스레드 코드
스레드 코드는 함수 호출을 설정하고 정리하는 모든 책임을 호출된 코드에 부여한다. 호출 코드는 호출할 서브루틴을 나열하는 것 외에는 아무것도 하지 않는다. 이렇게 하면 모든 함수 설정 및 정리 코드가 함수의 프롤로그와 에필로그라는 한 곳에 배치되며, 함수가 호출되는 많은 곳에 분산되지 않는다. 이는 스레드 코드를 가장 압축적인 호출 규약으로 만든다.
스레드 코드는 모든 인수를 스택으로 전달한다. 모든 반환 값은 스택으로 반환된다. 이로 인해 순진한 구현은 더 많은 값을 레지스터에 유지하는 호출 규약보다 느려진다. 그러나 상위 스택 값 중 일부(특히 반환 주소)를 레지스터에 캐시하는 스레드 코드 구현은 항상 스택에 반환 주소를 푸시하고 팝하는 서브루틴 호출 규약보다 일반적으로 빠르다.[22][23][24]
PL/I
PL/I 언어로 작성된 프로그램의 기본 호출 규약은 모든 인수를 참조에 의해 전달하지만, 다른 규약도 선택적으로 지정할 수 있다. 인수는 다른 컴파일러 및 플랫폼에 따라 다르게 처리되지만, 일반적으로 인수 주소는 메모리의 인수 목록을 통해 전달된다. 최종적인 숨겨진 주소는 반환 값을 포함할 영역을 가리키도록 전달될 수 있다. PL/I가 지원하는 다양한 데이터 유형 때문에 데이터 서술자도 문자 또는 비트 문자열의 길이, 배열의 차원 및 경계(도프 벡터), 자료 구조의 레이아웃 및 내용 등을 정의하기 위해 전달될 수 있다. 상수이거나 호출된 프로시저가 예상하는 인수의 유형과 일치하지 않는 인수에 대해서는 더미 인수가 생성된다.
같이 보기
각주
외부 링크
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads