다음을 통해 공유


인라인 함수(C++)

inline 키워드는 컴파일러가 해당 함수에 대한 각 호출 대신 함수 정의 내의 코드를 대체하도록 제안합니다.

이론적으로 인라인 함수를 사용하면 함수 호출과 연관된 오버헤드가 제거되어 프로그램 속도가 더 빨라질 수 있습니다. 함수를 호출하려면 스택에 반환 주소를 푸시하고, 인수를 스택에 푸시하고, 함수 본문으로 이동한 다음, 함수가 완료되면 반환 명령을 실행해야 합니다. 이 프로세스는 함수를 인라인 처리하여 제거됩니다. 또한 컴파일러는 인라인으로 확장된 함수와 그렇지 않은 함수를 최적화하는 다른 기회도 있습니다. 인라인 함수의 단점은 프로그램의 전체 크기가 증가할 수 있다는 것입니다.

인라인 코드 대체는 컴파일러의 재량에 따라 수행됩니다. 예를 들어 컴파일러는 함수 주소를 가져온 경우나 함수가 너무 크다고 판단하는 경우 함수를 인라인 처리하지 않습니다.

클래스 선언의 본문에 정의된 함수는 암시적으로 인라인 함수입니다.

예시

다음 클래스 선언에서 Account 생성자는 클래스 선언의 본문에 정의되어 있으므로 인라인 함수입니다. 멤버 함수 GetBalance, DepositWithdraw는 정의에서 inline으로 지정됩니다. inline 키워드는 클래스 선언의 함수 선언에서 선택 사항입니다.

// account.h
class Account
{
public:
    Account(double initial_balance)
    {
        balance = initial_balance;
    }

    double GetBalance() const;
    double Deposit(double amount);
    double Withdraw(double amount);

private:
    double balance;
};

inline double Account::GetBalance() const
{
    return balance;
}

inline double Account::Deposit(double amount)
{
    balance += amount;
    return balance;
}

inline double Account::Withdraw(double amount)
{
    balance -= amount;
    return balance;
}

참고 항목

클래스 선언에서는 함수가 inline 키워드 없이 선언되었습니다. inline 키워드는 클래스 선언에서 지정할 수 있으며 결과는 동일합니다.

주어진 인라인 멤버 함수는 모든 컴파일 단위에서 동일한 방식으로 선언되어야 합니다. 인라인 함수의 정의는 정확히 하나만 있어야 합니다.

해당 함수에 대한 정의에 inline 지정자가 포함되지 않는 한 클래스 멤버 함수는 기본적으로 외부 링크로 설정됩니다. 앞의 예에서는 inline 지정자를 사용하여 이러한 함수를 명시적으로 선언할 필요가 없음을 보여줍니다. 함수 정의에서 inline을 사용하면 컴파일러에 함수를 인라인 함수로 처리하라고 제안합니다. 그러나 함수를 호출한 후에는 해당 함수를 inline으로 다시 지정할 수 없습니다.

inline, __inline__forceinline

inline__inline 지정자는 함수가 호출되는 각 위치에 함수 본문의 복사본을 삽입하도록 컴파일러에 제안합니다.

삽입(인라인 확장 또는 인라인 처리라고 함)은 컴파일러의 비용/이익 분석에서 가치가 있다고 보이는 경우에만 일어납니다. 인라인 확장으로 코드 크기가 커질 수 있지만 함수 호출 오버헤드는 최소화할 수 있습니다.

__forceinline 키워드는 비용/이익 분석을 재정의하고 대신 프로그래머의 판단에 의존합니다. __forceinline을 사용할 때는 주의해야 합니다. __forceinline을 무분별하게 사용하면 코드가 더 커져 성능이 조금밖에 향상되지 않거나, 경우에 따라 더 큰 실행 파일의 페이징 증가와 같은 이유로 성능이 저하될 수도 있습니다.

컴파일러는 인라인 확장 옵션과 키워드를 제안으로 처리합니다. 함수가 인라인 처리된다는 보장은 없습니다. __forceinline 키워드를 사용해도 컴파일러에 특정 함수를 강제로 인라인 처리하게 할 수 없습니다. /clr로 컴파일할 때 컴파일러는 함수에 적용된 보안 특성이 있으면 함수를 인라인 처리하지 않습니다.

이전 버전과의 호환성을 위해 _inline_forceinline은 각각 __inline__forceinline의 동의어입니다. 단 컴파일러 옵션 /Za(언어 확장 사용 안 함)가 지정된 경우는 예외입니다.

inline 키워드는 인라인 확장이 선호된다는 것을 컴파일러에 알려줍니다. 하지만 컴파일러는 이를 무시할 수 있습니다. 이 동작이 발생할 수 있는 두 가지 경우는 다음과 같습니다.

  • 재귀 함수.
  • 변환 단위의 다른 위치에서 포인터를 통해 참조되는 함수.

이러한 이유는 다른 이유와 마찬가지로 컴파일러에 의해 결정된 대로 인라인 처리에 방해가 될 수 있습니다. 함수가 인라인 처리되게 하는 데 inline 지정자에 의존하지 마세요.

컴파일러는 헤더 파일에 정의된 인라인 함수를 확장하는 대신 둘 이상의 변환 단위에서 호출 가능한 함수로 만들 수 있습니다. 컴파일러는 링커에 대해 생성된 함수를 표시하여 ODR(One-definition-rule) 위반을 방지합니다.

일반 함수와 마찬가지로 인라인 함수에서 인수 평가에 정의된 순서는 없습니다. 실제로 인수 평가 순서가 일반 함수 호출 프로토콜을 사용하여 전달될 때와 다를 수 있습니다.

인라인 함수 확장이 실제로 발생하는지 여부에 영향을 주려면 /Ob 컴파일러 최적화 옵션을 사용합니다.
/LTCG는 소스 코드에서의 요청 여부에 관계없이 모듈 간 인라인 처리를 수행합니다.

예 1

// inline_keyword1.cpp
// compile with: /c
inline int max(int a, int b)
{
    return a < b ? b : a;
}

클래스의 멤버 함수는 inline 키워드를 사용하거나 클래스 정의 내 함수 정의를 배치하여 인라인으로 선언할 수 있습니다.

예제 2

// inline_keyword2.cpp
// compile with: /EHsc /c
#include <iostream>

class MyClass
{
public:
    void print() { std::cout << i; }   // Implicitly inline

private:
    int i;
};

Microsoft 전용

__inline 키워드는 inline과 동일합니다.

__forceinline 경우에도 다음과 같은 경우 컴파일러는 함수를 인라인 처리할 수 없습니다.

  • 함수 또는 해당 호출자가 /Ob0(디버그 빌드에 대한 기본 옵션)으로 컴파일됩니다.
  • 함수 및 호출자가 다양한 형식의 예외 처리(한 경우 C++ 예외 처리, 다른 경우 구조적 예외 처리)를 사용합니다.
  • 함수에 가변 인수 목록이 있습니다.
  • /Ox, /O1 또는 /O2로 컴파일되지 않은 한 함수는 인라인 어셈블리를 사용합니다.
  • 함수는 재귀적이며 #pragma inline_recursion(on)이 설정되지 않습니다. pragma를 사용하면 재귀 함수가 기본 깊이 16번 호출로 인라인 처리됩니다. 인라인 깊이를 줄이려면 inline_depth pragma를 사용합니다.
  • 가상 함수이며 실제로 호출됩니다. 가상 함수에 대한 직접 호출은 인라인 처리할 수 있습니다.
  • 프로그램에서 함수의 주소를 사용하고 함수에 대한 포인터를 통해 호출합니다. 주소가 사용된 함수에 대한 직접 호출은 인라인 처리할 수 있습니다.
  • 또한 함수는 naked __declspec 한정자를 사용하여 표시됩니다.

컴파일러가 __forceinline으로 선언된 함수를 인라인 처리할 수 없으면 다음 경우를 제외하고 수준 1 경고가 생성됩니다.

  • 함수가 /Od 또는 /Ob0을 사용하여 컴파일됩니다. 이러한 경우에는 인라인 처리가 필요하지 않습니다.
  • 함수는 포함된 라이브러리나 다른 변환 단위에서 외부적으로 정의되거나, 가상 호출 대상 또는 간접 호출 대상입니다. 컴파일러는 현재 변환 단위에서 찾을 수 없는, 인라인이 아닌 코드를 식별할 수 없습니다.

재귀 함수는 inline_depth pragma에서 지정한 깊이까지 최대 16개의 호출까지 인라인 코드로 대체될 수 있습니다. 해당 깊이 이후 재귀 함수 호출은 함수의 인스턴스 호출로 처리됩니다. 인라인 추론에서 재귀 함수를 검사하는 깊이는 16을 초과할 수 없습니다. inline_recursion pragma는 현재 확장 중인 함수의 인라인 확장을 제어합니다. 관련 정보는 인라인 함수 확장(/Ob) 컴파일러 옵션을 참조하세요.

Microsoft 전용 종료

inline 지정자 사용에 대한 자세한 내용은 다음을 참조하세요.

인라인 함수 사용 시기

인라인 함수는 데이터 멤버에 대한 액세스를 제공하는 함수와 같은 작은 함수에 가장 적합합니다. 짧은 함수는 함수 호출의 오버헤드에 민감합니다. 긴 함수는 호출 및 반환 시퀀스에서 비례적으로 적은 시간이 들고 인라인 처리로 인한 이점이 크지 않습니다.

Point 클래스는 다음과 같이 정의할 수 있습니다.

// when_to_use_inline_functions.cpp
// compile with: /c
class Point
{
public:
    // Define "accessor" functions
    // as reference types.
    unsigned& x();
    unsigned& y();

private:
    unsigned _x;
    unsigned _y;
};

inline unsigned& Point::x()
{
    return _x;
}

inline unsigned& Point::y()
{
    return _y;
}

좌표 조작이 이러한 클래스의 클라이언트에서 상대적으로 일반적인 작업이라고 가정하면 두 접근자 함수(앞의 예에서 xy)를 inline으로 지정하여 일반적으로 다음에서 오버헤드를 줄입니다.

  • 함수 호출(개체의 주소를 스택에 전달 및 배치하는 매개 변수 포함)
  • 호출자의 스택 프레임의 보존
  • 새 스택 프레임 설정
  • 반환 값 전달
  • 이전 스택 프레임 복원
  • Return

인라인 함수와 매크로 비교

매크로는 inline 함수와 몇 가지 공통점이 있습니다. 하지만 두 가지 중요한 차이점이 있습니다. 다음 예제를 참조하세요.

#include <iostream>

#define mult1(a, b) a * b
#define mult2(a, b) (a) * (b)
#define mult3(a, b) ((a) * (b))

inline int multiply(int a, int b)
{
    return a * b;
}

int main()
{
    std::cout << (48 / mult1(2 + 2, 3 + 3)) << std::endl; // outputs 33
    std::cout << (48 / mult2(2 + 2, 3 + 3)) << std::endl; // outputs 72
    std::cout << (48 / mult3(2 + 2, 3 + 3)) << std::endl; // outputs 2
    std::cout << (48 / multiply(2 + 2, 3 + 3)) << std::endl; // outputs 2

    std::cout << mult3(2, 2.2) << std::endl; // no warning
    std::cout << multiply(2, 2.2); // Warning C4244	'argument': conversion from 'double' to 'int', possible loss of data
}
33
72
2
2
4.4
4

다음은 매크로와 인라인 함수 간의 몇 가지 차이점입니다.

  • 매크로는 항상 인라인으로 확장됩니다. 그러나 인라인 함수는 컴파일러에 의해 최적이라고 판단될 때만 인라인 처리가 이루어집니다.
  • 매크로는 예기치 않은 동작을 발생시킬 수 있고, 이로 인해 미묘한 버그가 발생할 수 있습니다. 예를 들어 식 mult1(2 + 2, 3 + 3)이 11로 계산되는 2 + 2 * 3 + 3으로 확장되지만, 예상 결과는 24입니다. 유효해 보이는 수정 방법은 함수 매크로의 두 인수 주위에 괄호를 추가하여 #define mult2(a, b) (a) * (b)의 결과를 가져오는 것입니다. 이렇게 하면 당면 문제는 해결되지만, 더 큰 식의 일부인 경우에는 여전히 놀라운 동작이 발생할 수 있습니다. 이는 앞의 예에서 설명했으며, 매크로를 #define mult3(a, b) ((a) * (b))로 정의하여 문제가 해결될 수 있습니다.
  • 인라인 함수는 컴파일러의 의미 체계 처리의 대상이 되지만, 전처리기는 이와 동일한 이점 없이 매크로를 확장합니다. 매크로는 형식이 안전하지 않지만 함수는 형식이 안전합니다.
  • 인라인 함수에 인수로 전달된 식은 한 번 계산됩니다. 매크로에 인수로 전달된 식은 경우에 따라 여러 번 계산할 수 있습니다. 다음 예를 살펴보세요.
#include <iostream>

#define sqr(a) ((a) * (a))

int increment(int& number)
{
    return number++;
}

inline int square(int a)
{
    return a * a;
}

int main()
{
    int c = 5;
    std::cout << sqr(increment(c)) << std::endl; // outputs 30
    std::cout << c << std::endl; // outputs 7

    c = 5;
    std::cout << square(increment(c)) << std::endl; // outputs 25
    std::cout << c; // outputs 6
}
30
7
25
6

이 예에서는 식 sqr(increment(c))((increment(c)) * (increment(c)))로 확장할 때 함수 increment가 두 번 호출됩니다. 이로 인해 increment의 두 번째 호출이 6을 반환하고, 따라서 식은 30으로 계산됩니다. 부작용이 포함된 식은 매크로에 사용될 때 결과에 영향을 줄 수 있습니다. 완전히 확장된 매크로를 검사하여 동작이 의도된 것인지 확인하세요. 대신 인라인 함수 square가 사용된 경우 increment 함수는 한 번만 호출되고 25라는 올바른 결과를 얻게 됩니다.

참고 항목

noinline
auto_inline