반응형

http://blog.naver.com/xogml_blog/130144674709


인라인 함수란 무엇인가 ? 

우선 일반 함수를 알아보면, 일반 함수는 우선 매개변수를 스택에 집어넣고, 함수를 호출한다. 다시말하면


//int x86     *.cpp

int add(int a, int b) { return a + b; } 이러한 코드는

int main(void) {

add(1, 2);

return 0;

}


//int x86     *.asm

push 2

push 1

call ?add@@YAHHH@Z ; add1

add esp, 8

mov DWORD PTR _n1$[ebp], eax

이런식으로 호출된다.

또한 여기에는 적지않았지만 리턴값을 위한 스택 push, pop도 있고 라턴후 스택정리를 위한 pop도 있다..

이러한 함수 호출은 스택에 접근 하기 떄문에, 다시말하면 메모리에 접근하기 때문에 속도 저하가 생길수 있다.

이러한 스택접근, 즉 메모리 접근을 피하는 방법은 무엇이 있을까? 에서 나온 한편의 해결책이 인라인 함수이다.

위의 내용을 자세히 보려면 [calling convention] 을 보면 된다.



원리 ?

기계어를 통채로 치환한다. 즉 함수 호출없이 코드를 가져다 붙인다. ㅋ 그래서 스택접근이 없기 떄문에 메모리 접근이

없고 결국 속도면에서 이득을 얻을수 있다.



사용 ?

inline returnType functionName(argument) {

//routine...

}


ex)


//main.cpp

inline int add(int a, int b) {

return a + b;

}


int main(void) {

add(1, 2);

return 0;

}

이런식으로 사용하면 된다. 이와같이 인라인 함수를 사용해서 컴파일 하게 되면


//main.cpp

mov eax, 1

add eax, 2

mov DWORD PTR _n2$[ebp], eax

어셈 코드가 위와같이 나오게 된다. 즉 push, pop가 없으므로 성능에 향상이 있다.



문제점 1. 코드의 길이가 길어진다 ?

직관적으로 보면 메서드의 기계어를 통채로 때어다가 붙이기 때문에, 목적코드가 길어질수 있다....

라고 Effective C++이라는 책에 나오고 있지만, 오히려 목적코드를 작게 해줄수도 있다고 적혀잇다. 비교해보자

a) 일반 함수를 사용할경우 : 매개변수 push + 복귀주소 eip push + 함수호출 call

b) 인라인 함수 사용할경우 : 함수의 목적코드가 통째로 와서 붙음.


정리하면 함수의 크기가 "매개변수 push + 복귀주소 eip push + 함수호출 call" 보다 클경우 목적 코드의 길이가 길어지고

작을경우에는 오히려 목적 코드의 길이가 작아진다.


따라서 함수가 복잡할경우에는 인라인 함수의 사용을 줄여야 한다. 하지만 단순한 함수일경우 무조건 인라인이다.



문제점 2. 디버깅이 어렵다.

당연한 이야기이다. 함수 모듈화의 장점은 재활용성과 더불어 코드 가독성에 있다. 하지만 call-return 구조를 포기하되 성능을

택하였으므로 코드가 보기 어려워지고 결과적으로 디버깅이 어렵게 된다.


더불어 vs에서는 최적화 옵션때문에 release 일때만 인라인으로 처리하고(어셈을 만들고), debug 모드일경우에는 일반 함수로 처리. 



문제점 3. inline 함수는 Internal Linkage 이다.

다시말해 인라인 함수는 내부연결이다. 더 나가기전에 internal linkage에 대해 알아보면


a. Internal Linkage 

- 영역의 심볼(전역변수, 함수)이 자신을 선언한 컴파일 단위(파일)에서만 사용 가능.

-> static 전역변수, inline 함수, function template, 매크로

-> C++ 문법의 const 


b. External Linkage

- 영역의 심볼이 프로젝트 내의 모든 컴파일 단위에서 사용 가능

-> 일반 전역변수, 일반 함수

-> C 문법의 const

※ 단 C++문법의 const도 C의 const처럼 External Linkage로 쓸수 있다는 밥법이 있다.



즉 inline 함수는 내부 연결의 속성을 갖기 때문에 이런것이 불가능 하다.


//add.cpp

inline int add(int a, int b) {

return a + b;

}


//main.cpp

inline int add(int a, int b);


int main(void) {

add(1, 2);

return 0;

}

다시말해 add.cpp 에서 작성한 인라인 add함수는 자신의 컴파일 단위, 다시말해 add.cpp에서만 효력한다. 그렇다면 어떻게 해야 할까?

해법은 헤더파일, 즉 헤더파일에 인라인 함수의 선언과 구현을 다 해주고 여러 파일들에서 이 헤더를 추가하여 사용하면 된다.



문제점 4. 인라인 함수를 함수포인터로 사용 ?

볼것도 없이 우선 해보자


//main.cpp

inline int add(int a, int b) {

return a + b;

}


int main(void) {

int (*pAdd)(int, int) = &add;

pAdd(1, 2);

return 0;

}

이 코드의 목적코드를 보자. 과연 함수포인터르 받은 인라인 함수가 사용 되었을까 ? 


; 5    : int main(void) {


push ebp

mov ebp, esp

push ecx


; 6    :  int(*pAdd)(int, int) = &add;


mov DWORD PTR _pAdd$[ebp], OFFSET ?add@@YAHHH@Z ; add


; 7    :  pAdd(1, 2);


push 2

push 1

call DWORD PTR _pAdd$[ebp]

add esp, 8


; 8    :  return 0;

어 사용 안됬다. 다시말해서 인라인 함수를 함수포인터로 받을수는 있지만, 함수 포인터로 사용하면 일반 함수처럼 사용된다.

뭔상관이야 ? 할수도 있는데 이런 경우를 생각해보자.

누구나 c언어를 배울때 stdlib의 퀵소트를 사용해본적이 있을것이다. 


void qsort(void * base, size_t num, size_t size, int(* comparator)(const void *, const void *));

여기서 세번째 함수로 넘겨주는것이 함수포인터이다. C언어에서 

1. 변하지 않은 전체 알고리즘과, 변해야 하는 정책을 분리하는 것이 좋다.

2. C에서는 변하는 것을 함수 인자화, 즉 함수 포인터로 표현하면 된다.


퀵소트의 알고리즘을 보면 선택된 인자 두개를 비교하는 루틴이 있다는것을 알것이다. 이 루틴에 따라서 오름차순, 내림차순이

결정되는데, 즉 무지막지하게 저 comparator가 호출되는것이다.


간혹가다 "아 이 comparator가 많이 호출되고 단순한 함수니까 inline으로 만들자" 라는 사람이 있을텐데...

이쯤되면 눈치 챘을것이다. 함수포인터는 inline으로 사용될수 없다.


그럼 해결책은 ? 아쉽게도 C언어 에서는 없다....

단 C++에서는 함수객체라는 이름으로 해결방안이 있다. 

반응형

+ Recent posts