Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
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
Archives
Today
Total
관리 메뉴

로또

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

책/Effective C++

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

아롱로또 2023. 11. 29. 13:10

다루는 내용

인라인 함수의 특징과 장단점에 대해 알아보자.

C++에서 함수의 호출

  1. 스택에 함수로 전달할 매개변수와 함께 호출이 끝난 뒤 돌아갈 반환 주소값을 저장한다.
  2. 프로그램의 제어가 함수의 위치로 넘어가 함수 내에 선언된 지역변수도 스택에 저장한다.
  3. 함수의 모든 코드를 실행하고, 실행이 전부 끝나면 반환값을 넘겨준다.
  4. 프로그램의 제어는 스택에 저장된 돌아갈 반환 주소값으로 이동하여, 스택에 저장된 함수 호출 정보를 제거한다.

인라인 함수란?

호출될 때 위와 같은 일련의 과정을 거치지 않고, 함수의 모든 코드를 호출된 자리에 바로 삽입하는 방식의 함수.

 

인라인 함수의 장점

  • 함수처럼 보이고 함수처럼 동작한다.
  • 매크로보다 안전하고 쓰기 용이하다.
  • 함수 호출 시 발생하는 오버헤드가 면제된다.
  • 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기 용이하다. (대부분의 컴파일러는 아웃라인 함수 호출에 대해 문맥별 최적화 X)

인라인 함수는 항상 좋을까?

인라인 함수는 함수 호출문을 그 함수의 본문으로 바꿔치기 하는 것이다.

때문에 목적 코드의 크기가 커져 메모리가 제한된 컴퓨터에서 인라인 함수를 남발하는 것은 프로그램의 크기가 해당 컴퓨터에서 사용 가능한 공간을 초과할 수 있다. 또한 인라인 함수로 인해 부풀려진 코드는 페이징 횟수가 늘어나고, 명령어 캐시 적중률이 떨어질 가능성도 높아진다.

대신, 본문의 길이가 굉장히 짧은 함수에 대해 인라인을 적용하면, 함수 본문에 대해 만들어지는 코드의 크기가 함수 호출문에 대해 만들어지는 코드보다 작아질 수 있다. 이러한 경우에는 반대로 목적 코드의 크기도 작아지며 명령어 캐시 적중률도 높아진다.

inline 함수의 사용

암시적 inline

다음과 같이 클래스 정의 안에 함수를 바로 정의해 넣으면 컴파일러는 해당 함수를 inline 함수의 후보로 둔다.

class Person{
public:
    int GetAge() const{return age;}
private:
    int age;
};

 

명시적 inline

inline keyword를 사용한다. 실제 표준 라이브러리의 max template은 다음과 같이 정의되어 있다고 한다.

template<typename T>
inline const T& std::max(const T& a, const T& b)
{ return a < b > b : a; }

인라인 함수와 템플릿은 대개 헤더 파일 안에 정의한다.

대부분의 빌드 환경에서 인라인을 컴파일 도중에 수행하기 때문에 인라인 함수는 대체적으로 헤더 파일 안에 들어 있어야 하는게 맞다. 인라인 함수 호출을 해당 함수의 본문으로 바꾸기 위해서는 해당 함수가 어떤 형태인지 알아야 하기 때문이다.

 

템플릿 역시, 대체적으로 헤더 파일에 들어 있어야 맞다. 템플릿이 사용되는 부분에서 해당 템플릿을 인스턴스로 만들려면 그것이 어떻게 생겼는지를 컴파일러가 알아야 하기 때문이다.

 

템플릿 인스턴스화(컴파일러가 함수 템플릿으로부터 실제 C++ 함수를 만드는 과정)는 inline과 관련이 없다. 어떤 템플릿을 만들 때, 해당 템플릿으로부터 만들어지는 모든 함수를 인라인 함수로 만들고 싶을 때, 템플릿에 inline을 붙여 선언할 수 있을 뿐이다.

 

inline은 컴파일러 선에서 무시할 수 있는 요청이다.

inline은 컴파일러에게 요청하는 것이다. 위처럼 inline을 붙이지 않은 함수에 대해서도 컴파일러가 눈치껏 inline으로 만드는 경우도 있고, 명시적으로 요청할 수도 있다.

 

inline 함수로 선언되어 있더라도 루프가 포함되거나 재귀 함수인 경우처럼 컴파일러가 보기에 복잡한 함수는 inline하지 않는다.

인라인 함수의 주소를 취하는 코드가 있으면 컴파일러는 해당 코드를 위해 아웃라인 함수 본문을 만들 수 밖에 없을 것이다.

인라인 함수를 함수 포인터로 호출하는 경우도 대개 인라인되지 않는다.

inline void f(){...} // 컴파일러가 inline해준다고 가정하자.
void (*pf)() = f; // 함수포인터 pf
f(); // 평범한 함수 호출, inline 될 것이다.
pf(); // 함수 포인터를 통해 호출되므로 인라인되지 않는다.

 

간단한 함수라도 가상 함수라면 절대로 inline하지 않는다. virtual은 "어떤 함수를 호출할 지 결정하는 작업을 런타임에 한다."는 것이고, inline은 "함수 호출 위치에 호출된 함수를 끼워 넣는 작업을 컴파일 타임에 한다."는 뜻이기 때문이다.

 

인라인 함수가 실제로 인라인되는지에 대한 여부는 개발자가 사용하는 빌드 환경에 달렸다. 그 중에서도 컴파일러가 결정한다.

 

생성자와 소멸자의 inline

생성자와 소멸자는 inline 하기에 좋지 않은 함수이다.

class Base{
public:
    ...
private:
    string bm1, bm2;
};

class Derived{
public:
    Derived(){} // 비어있는 생성자.
private:
    string dm1, dm2, dm3;
}

Derived 생성자는 아무것도 없는 것이 inline 하기에 정말 좋아보인다. 하지만 실제로 비어있지는 않을 것이다.

C++는 객체가 생성되고 소멸될 때 발생하는 일들에 대해 다음과 같이 여러 가지를 보장한다.

  • new/delete 시 생성자가 자동으로 초기화를 하고, 소멸자를 호출한다.
  • 객체를 생성할 때 기본 클래스와 객체의 데이터 멤버가 자동으로 생성되고, 소멸될 때 이에 반대되는 순서대로 소멸 과정이 발생한다.
  • 객체가 생성되는 도중에 예외가 발생해도, 이미 생성된 부분에 대해 깔끔히 소멸시킨다.

빈 생성자임에도 위와 같은 작업들은 우리가 모르게 자동으로 발생한다. 즉, 우리 눈에 보이지 않지만 이러한 일을 가능하게 하는 코드들은 우리의 프로그램에 포함되어 있을 것이고 이는 컴파일러가 만들어 넣어주는 것이다.

Derived::Derived()
{
    Base::Base();
    try{dm1.string::string()}
    catch(...){
        Base::~Base();
        throw;
    }
    try{dm2.string::string()}
    catch(...){
        dm1.string::~string();
        Base::~Base();
        throw;
    }
    try{dm3.string::string()} // dm3 생성 시도
    catch(...){ // 예외 발생 시
        dm2.string::~string(); // dm2 소멸
        dm1.string::~string(); // dm1 소멸
        Base::~Base(); // 기본 클래스 소멸
        throw; // 예외 전파
    }
}

컴파일러가 만든 실제 코드가 위처럼 무식하게 예외 처리를 하지 않겠지만, 추가한 코드가 하는 일에 대해서 만큼은 유사할 것이다.

위 코드에서 Base 생성자가 인라인된다고 가정해보자. 그렇다면 해당 함수에 삽입되어 있던 코드들도 전부 Derived 생성자에 끼어들어갈 것이고, string 생성자도 인라인이라면 Derived 생성자는 동일한 함수 본문을 5개(Base의 string 2개, Derived의 string3개)씩이나 갖게 될 것이다.

 

결론적으로, 생성자나 소멸자의 inline화는 좋지 않다고 말할 수 있다.

 

이것만은 잊지 말자!

  • 함수 인라인은 작고, 자주 호출되는 함수에 대해서만 하는 것으로 묶어두자. 이렇게 하면 디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는 코드 부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 많아진다.
  • 함수 템플릿이 대개 헤더 파일에 들어간다는 일반적인 부분만 생각해서 이들을 inline으로 선언하면 안된다.

참고 자료

https://tcpschool.com/cpp/cpp_cppFunction_inlineFunction

 

코딩교육 티씨피스쿨

4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등

tcpschool.com