티스토리 뷰

728x90

인라인 함수는 미주알고주알 따져서 이해해 두자

인라인 함수의 정의

함수 호출 문을 그 함수의 본문으로 바꿔치기하는 함수.
컴파일러에 대한 개발자의 요청으로 무시될 수도 있다.

 

inline 함수

 

1
2
3
4
5
6
7
8
9
10
11
12
13
inline void foo()
{
    cout << "foo" << endl;
}
void main()
{
    foo();
}
//컴파일 과정에서 함수본문으로 대체...
void main()
{
    cout << "foo" << endl;
}
cs

 

장점 :
함수처럼 보이고 함수처럼 동작한다.
매크로보다 안전하다.
오버헤드 걱정이 없다.
함수 호출비용이 면제(함수를 찾고 만들고 하는 시간 등...)
컴파일러가 함수 본문에 대해 최적화를 걸기가 용이해진다

단점:
목적 코드가 커진다.(메모리가 제한된 컴퓨터에서 남발하면 자원을 바닥낼 수도 있다.)
가상 메모리를 쓰는 환경일지라도 성능의 걸림돌이 되기 쉽다.
페이징 횟수가 늘어나고, 명령어 캐시 적중률(*)이 떨어질 가능성이 높아진다.
이런 문제들로 인해 수행 성능이 저하된다.

단, 본문의 길이가 굉장히 짧을 경우, 일반 함수로 만들었을 때보다 목적 코드의 크기가 작아질 수 있고, 명령어 캐시 적중률(*)도 높아진다. 수행 성능이 향상된다.

*) 명령어 캐시 적중률이란?? CPU가 캐시에서 해당 코드의 내용을 찾는 비율을 이야기한다.

CPU가 캐시의 내용을 연산하려고 하는데, 그 내용이 캐시에 없을 때가 많으면 명령어 캐시 적중률이 떨어진다고 한다.

 

오해

표준 라이브러리 참고 시 생길 수 있는 오해.... 템플릿 함수는 반드시 인라인이어야 한다????

인라인과 템플릿은 아무런 관계가 없다.

인라인 함수의 위치: 인라인을 컴파일 도중에 수행하므로, 대체적으로 헤더 파일에 들어있어야 한다.

템플릿 함수: 대체적으로 헤더 파일에 들어 있어야 한다.

템플릿을 구체화하려면 그것이 어떻게 생겼는지 컴파일러가 알아야 한다.

 

인라인이 컴파일러선에서 무시되는 경우

컴파일러의 입장에서 자신이 보기에 복잡한 함수는 인라인에서 제외된다.(루프가 들어 있거나 재귀 함수의 경우)

가상 함수를 호출한다면 인라인에서 제외된다.

이는, 가상 함수의 호출 시점이 컴파일 시가 아닌 작업 실행 중이기 때문이다.

가상 함수가 인라인 함수가 되지 않는 이유도 된다.

인라인 조건이 완벽하지만 본문에 대한 코드를 만드는 경우(함수로서 동작하게 될 경우)

Ex) 함수 포인터에 의한 호출 ( inline void foo();

void (*pf)() = foo;

foo(); <----- 인라인 된다.

pf(); <----- 코드를 만들어 함수를 호출시킨다.

 

잘못된 인라인은 예외

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base
{
public:
private:
    std::string bm1,bm2;
};
 
class Derived : public Base
{
public:
    Derived() {}    // 비어 있지만 컴파일러가 만들어 내는 코드가 있다.
private:
    std::string bm1,bm2,bm3;
};
cs

생성자, 소멸자의 인라인 사용은 금하자.(컴파일 단계에서 실제로 만들어 내는 코드가 있다.)

빈 생성자의 컴파일 시의 실제 코드 동작

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
Derived::Derived()
{
    Base::Base();
    try
        dm1.std::string::string(); 
    }catch (...) {
        Base::~Base();
        throw;
    }
    try{
        dm2.std::string::string(); 
    }catch (...) {
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
    try
        dm3.std::string::string(); 
    }catch (...) {
        dm2.std::string::~string();
        dm1.std::string::~string();
        Base::~Base();
        throw;
    } 
}
 
//만약, Base의 생성자까지 인라인 되었다면... 어떤 동작이 되는것인가?
Derived::Derived(){
//    Base::Base();                    <--------------- 이구문은 지워지고...
    try{
        dm1.std::string::string(); 
    }catch (...){
        Base::~Base();
        throw;
    }    
    
    try{
        dm2.std::string::string(); 
    }catch (...){
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
    
    try{
        dm1.std::string::string(); 
    }catch (...) {
           Base::~Base();
           throw;
    }
    try
        dm2.std::string::string(); 
    }catch (...){
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
    
    try{
         dm3.std::string::string(); 
    }catch (...) {
        dm2.std::string::~string();
        dm1.std::string::~string();
        Base::~Base();
        throw;
    }
}
 
cs

결론

◎ 인라인 함수는 대부분의 디버거가 무척 버거워한다.


◎ 인라인 사용 전략 : 어떤 함수를 인라인 선언하고 선언하지 말아야 할 것인가?

1) 아무것도 인라인 하지 않는다.
- 꼭 인라인 해야 하는 함수만 인라인 하자.(용도에 따라 클래스 내에 friend 함수의 정의가 필요하거나..)
- 정말 단순한 함수만 인라인 선언을 하자.


2) 인라인을 주의해서 사용하는 버릇을 들이자
- 디버깅을 제대로 쓸 수 있도록 만들자.
- 정말 필요한 위치에 인라인 함수를 배치하자.

◎ 이것만은 잊지 말자!

* 함수 인라인은 작고, 자주 호출되는 함수에 대해서만 하는 것으로 묶어둡시다.
이렇게 하면 디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는
코드 부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 최고로 많아진다.


* 함수 템플릿이 대게 헤더 파일에 들어간다는 일반적인 부분만 생각해서 이들을 inline으로

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함
250x250