프로그래밍 언어/C & C++ 정리

함수와 스택 그리고 호출 스택

게임 개발 2024. 6. 25. 11:27

 

 

너무 짧은 코드의 함수화는 주소지를 찾아가야하기 때문에 비효율적이다.

하지만 inline 함수는 자주 쓰이기 때문에 짧은 코드를 활용하기 좋다.

 

C/C++ 및 어셈블러는 x64 환경에서

RSP의 현재 주소를 초과하는 모든 메모리는 휘발성으로 간주된다.

OS 또는 디버거는 사용자 디버그 세션 또는 인터럽트 처리기 중에 이 메모리를 덮어쓸 수 있다.

 

따라서 스택 프레임에 대한 값을 읽거나 쓰기 전에 항상 RSP를 설정해야 한다.

이 섹션에서는 로컬 변수에 대한 스택 공간 할당 및 alloca 내장 함수에 대해 설명한다.

 

스택 할당

 

함수의 프롤로그

1) 로컬 변수,

2) 저장된 레지스터,

3) 스택 매개 변수 및 4) 레지스터 매개변수에 대한 스택 공간을 할당한다.

 

매개 변수 영역은 항상 스택의 맨 아래에 있다(alloca가 사용되는 경우에도 마찬가지이다).

그러므로 모든 함수를 호출하는 동안 항상 반환 주소에 인접하게 된다.

 

4개 이상의 항목을 포함하지만

호출될 수 있는 함수에 필요한 모든 매개 변수를 저장할 수 있는 공간이 항상 충분하다.

경우 1) 매개 변수 자체가 스택으로 열리지 않는 경우에도

항상 레지스터 매개 변수에 공간이 할당된다.

즉 = 호출 수신자는 모든 매개 변수에 대한 공가닝 할당되도록 보장을 받는다.

 

호출된 함수가 인수 목록(va_list) 또는 개별 인수의 주소를 사용해야 하는 경우

인접한 영역을 실행할 수 있도록 레지스터 인수에 홈 주소가 필요하다.

또한 스택 영역은 썽크 실행 중 또는 디버깅 옵션으로

레지스터 인수를 저장하는 편리한 위치를 제공한다.

 


++ Plus ++

Thunk 썽크란,
기존의 서브루틴에 추가적인 연산을 삽입할 때 사용되는 서브루틴이다.
썽크는 주로 연산 결과가 필요할 때가지 연산을 지연시키는 용도로 사용되거나,
기존의 다른 서브루틴들의 시작과 끝 부분에 연산을 추가시키는 용도로 사용되는데,
컴파일러 코드 생성시와 모듈화 프로그래밍 방법론 등에서 좀 더 다양한 형태로 활용되기도 한다.

썽크 (Thunk)는 "고려하다"라는 영단어인 
"Think"의 은어 격 과거분사인 "Thunk"에서 파생된 단어인데,

연산이 철저하게 "고려된 후",
즉 실행이 된 후에와 썽크의 값이 가용해지는 데서 유래된 것이라고 볼 수 있다. 


++ Plus ++

서브루틴이란?

함수, 서브루틴, 루틴, 메서드, 프로시저는 소프트웨어에서
특정 동작을 수행하는 일정 코드 부분이다.
즉, '특정한 작업을 위해 재활용할 수 있도록 구현한 코드 블록'을 의미한다.

즉 서브루틴은 프로그램이 특정 태스크를 수행하기 위해 호출하는 일련의 명령어이다.

 

 

예를 들어 프롤로그 코드의 홈 주소에 저장되면 디버깅 중에 인수를 쉽게 찾을 수 있다.

 

호출된 함수에 4개 미만의 매개 변수가 있는 경우에도

이러한 4개 스택 위치는 호출된 함수가 유효하게 소유하며,

매개 변수 레지스터 값을 저장하는 것 외에도 다른 용도로 호출된 함수가 사용할 수 있다.

 

따라서 호출자는 함수 호출에서 이 스택 영역에 정보를 저장하지 않을 수 있다.

 

함수에서 공간을 동적으로 할당하는 경우(alloca),

비휘발성 레지스터를 프레임 포인터로 사용하여 스택의 고정 부분을 표시하고

프롤로그에서 레지스터를 저장하고 초기화해야 한다.

alloca를 사용하는 경우 동일한 호출 수신자에 대한 동일한 호출자의 호출에

레지스터 매개 변수에 대한 다른 홈 주소가 있을 수 있다.

 

스택은 항상 16바이트 맞춤 상태로 유지되지만,

예외 상황 1) 반환 주소가 푸시된 후와 같이 프롤로그 내부에서 지정된 경우

예외 상황 2) 함수 형식에 특정 프레임 함수 클래스용으로 표시된 경우는 예외이다.

 

예외 처리의 ex)

 

다음은 함수 A가 리프가 아닌 함수 B를 호출하는 스택 레이아웃의 예이다.

 

함수 A의 프롤로그는

스택의 맨 아래에 있는 B에 필요한

모든 레지스터 및 스택 매개 변수에 대한 공간을 이미 할당했다.

 

호출은 반환 주소를 푸시하고

B의 프롤로그는 로컬 변수 및 비휘발성 레지스터에 대한

공간과 함수를 호출하는 데 필요한 공간을 할당한다.

 

B가 alloca를 사용하는 경우 로컬 변수 / 비휘발성 레지스터 저장 영역과

매개 변수 스택 영역 사이에 공간이 할당된다.

 

 

함수 B가 다른 함수를 호출하면 반환 주소가 RCX의 홈 주소 바로 아래에 푸시된다.

 

 

동적 매개 변수 스택 영역 생성

 

 

프레임 포인터를 사용하는 경우 매개 변수 스택 영역을 동적으로 만들기 위한 옵션이 있다.

현재 x64 컴파일러에서는 이 작업이 수행되지 않는다.

 

 

함수 유형

 

기본적으로 두 가지 형식의 함수가 있다.

함수 구분 유형 1) 스택 프레임이 필요한 함수를 프레임 함수라고 한다.

함수 구분 유형 2) 스택 프레임이 필요하지 않은 함수를 리프 함수라고 한다.

 

프레임 함수는

1) 스택 공간을 할당하거나,

2) 다른 함수를 호출하거나,
3) 비휘발성 레지스터를 저장하거나,

4) 예외 처리를 사용하는 함수이다.

 

함수 프레임은 또한 함수 테이블 항목이 필요하다.

프레임 함수에는 이 호출 표준의 전체 기능을 사용할 수 있다.

 

프레임 함수가 다른 함수를 호출하지 않는 경우에는 스택을 맞출 필요가 없다.

(스택 할당 섹션 참조)

 

리프 함수는 함수 테이블 항목이 필요하지 않은 함수이다.

 

RSP를 비롯한 모든 비휘발성 레지스터를 변경할 수 없다.

이는 함수를 호출하거나 스택 공간을 할당할 수 없음을 의미한다.

함수 실행되는 동안 스택을 맞추지 않은 상태로 둘 수 있다.

 

 

malloc 맞춤

 

malloc은 기본 맞춤을 사용하고 할당된 메모리 양에 맞을 수 있는 개체를 저장하는데

적절하게 맞춘 메모리를 반환하도록 보장된다.

기본 맞춤은 맞춤 사양이 없는 구현에서 지원하는 가장 큰 맞춤보다 작거나 같은 맞춤이다.

64비트 플랫폼을 대상으로 하는 코드에서는 16바이트이다.

 

예를 들어 4 바이트 할당은 4 바이트 또는 더 작은 개체를 지원하는 경계에 맞춰진다.

 

Visual  C++에서는 맞춤 확장을 사용하는 형식(과다 정렬된 형식이라고 함)이 허용된다.

이 맞춤은 예를 들어 SSE형식 _m128 및 _m256 그리고

_declspec(align(n)) (여기서 n은 8보다 큼)을 사용하여 선언된 형식은 확장 맞춤을 사용한다.

확장 맞춤이 필요한 개체에 적합한 경계에서의 메모리 맞춤은 malloc에서 보장하지 않는다.

 

과다 정렬된 형식에 메모리를 할당하려면

_aligned_malloc 및 관련 함수를 사용하자.

 

 

alloca

 

_alloca는 16바이트로 맞춰야 하며,

프레임 포인터를 사용하는 데 추가적으로 필요하다.

 

스택 할당에 설명된 대로 할당된 스택에는

이후 호출된 함수의 매개 변수에 대한 공백이 포함되어야 한다.

 

 

 

 

디버거에서 호출 스택 보기 및 호출 스택 창 사용

 

 

호출 스택 창에서 현재 스택에 있는 함수 또는 프로시저 호출을 볼 수 있다.

호출 스택 창에는 메서드와 함수가 호출되는 순서가 표시된다.

호출 스택은 애플리케이션의 실행 흐름을 검사하고 파악할 수 있는 좋은 방법이다.

 

호출 스택의 일부에 대해 디버그 기호를 사용할 수 없는 경우

호출 스택 창에는 호출 스택의 해당 부분에 대해 올바르게 정보가 표시되지 않고

대신 다음과 같이 표시될 수 있다.

Frames below may be incorrect and/or missing, no symbols loaded for name.dll

출저 : 마이크로 소프트

 

화살표는 현재 실행 포인터가 있는 스택 프레임을 나타낸다.

기본적으로 이 스택 프레임의 정보는 소스, 로컬, 자동, 조사식디스어셈블리 창에 표시된다.

디버거 컨텍스트를 스택의 다른 프레임으로 변경하려면 다른 스택 프레임으로 전환한다.

 

디버깅하는 동안 호출 스택에서 예외 스택 프레임을 볼 수도 있다.

자세한 내용은 예외 도우미 호출 스택 보기를 참고하시오.

 

 

참고 사이트 - 호출 스택

 

https://learn.microsoft.com/ko-kr/visualstudio/debugger/how-to-use-the-call-stack-window?view=vs-2022

 

디버거에서 호출 스택 보기 - Visual Studio (Windows)

Visual Studio IDE(통합 개발 환경)의 호출 스택 창을 사용하여 현재 스택에 있는 함수 또는 프로시저 호출을 볼 수 있습니다.

learn.microsoft.com

 

참고 사이트

 

https://learn.microsoft.com/ko-kr/cpp/build/stack-usage?view=msvc-170

'프로그래밍 언어 > C & C++ 정리' 카테고리의 다른 글

파일 복사 프로그램  (0) 2024.07.09
파일 읽고 쓰기 - C언어  (0) 2024.07.05
동적 바인딩 정적 바인딩  (0) 2024.06.05
재귀함수  (1) 2024.04.19
L-value, R-value  (0) 2024.04.11