전에 작성한 게시글(https://plorence.kr/482)에서 일부 내용을 가져왔습니다.
일반적인 함수는 아래와 같이 동작합니다.
  1. 함수 호출 명령 도달
  2. 함수 호출 명령 다음에 있는 명령 주소를 메모리에 저장
  3. 스택에 매개변수 복사후 함수가 시작되는 메모리 위치로 점프
  4. 코드 수행
  5. 리턴값을 레지스터에 복사 후 저장해뒀던 명령 주소로 돌아옴

인라인 함수와 일반 함수의 차이를 확인하기 위해 프로젝트 설정하기

VS2017 기준입니다.
Visual studio 프로젝트 설정
프로젝트 속성 -> C++ -> 최적화로 들어가서 Ob1이나 Ob2를 선택하고 적용 -> 확인을 클릭합니다.
 

디스어셈블리, 메모리 뷰어를 통해 뭐가 다른지 확인하기

#include <iostream>
inline int func1(int x, int y) {
       return x * x + y * y;
}
int func2(int x, int y) {
       return x * x + y * y;
}
int main(void) {
       std::cout << func2(2, 5);
       std::cout << func1(10, 5);
       
}
테스트용 소스코드
 

일반 함수(인라인이 아닌 함수)

인라인 함수와 가장 큰 차이점이라면 call 명령어가 있습니다.
그로 인해 push도 있습니다.
회색 코드는 흰색 코드의 어셈블리 버전
push 5
push 2
call func2
함수 호출에 필요한 인자를 전달하고, 함수를 호출합니다.
add esp,8
mov esi,esp
함수 호출 후에 점프할 주소를 가져오는 듯 합니다.
스택의 가장 위에 들어있는 데이터를 가리키는 포인터에 8을 더하고 그 값을 esi에 대입합니다.
push eax
func2의 반환값이 eax에 담겨 있는데, 이 값을 cout의 연산자 오버로딩 멤버 함수 인자로 넘깁니다.
현재 레지스터가 가지고 있는 값
실제로 0x1D는 10진수로 29입니다.
 

인라인 함수

인라인 함수는 함수 호출 과정이 생략되면서 Call 명령어가 없습니다.
회색 코드는 흰색 코드의 어셈블리 버전
일반 함수는 Call이 3개, 인라인 함수는 Call이 2개라는 차이점이 있습니다.
mov eax,5
imul ecx,eax,5
eax 레지스터에 5를 대입하고 그다음 명령어에서 eax*5를 곱하여 ecx레지스터에 대입합니다.
imul 명령어는 아래와 같은 특징을 가지고 있습니다.
  • 음수를 포함한 수를 곱할 때 필요한 명령어
  • eax와의 연산만이 아닌 레지스터끼리의 곱, eax가 아닌 다른 레지스터와 메모리의 곱, 상수와의 곱을 가능하게 한다.
mov edx,0Ah
imul eax,edx,0Ah
edx 레지스터에 10을 대입하고, edx*10의 결과값을 eax 레지스터에 대입합니다.
그래서 ecx 레지스터에는 25, eax 레지스터에는 100을 가지고 있습니다.
각 레지스터가 가지고 있는 값
add eax,ecx
mov esi,esp
push eax
eax 레지스터가 가지고 있는 값과 ecx 레지스터가 가지고 있는 값을 더하여 eax 레지스터에 대입됩니다.
스택의 가장 위에 들어있는 데이터를 가리키는 포인터의 값을 esi에 대입합니다. (의미 없는걸로 보임)
최종 결과값이 담겨 있는 eax 레지스터의 값을 cout의 연산자 오버로딩 멤버 함수 인자로 넘깁니다.
 
결국엔 인라인 함수는 어셈블리에서 함수의 호출과정이 생략되어 있다는 점을 알 수 있습니다.

댓글을 달아 주세요