[C++] inline 함수와 static 함수의 비교

반응형

inline 함수 vs static 함수

특이하게도 inline 함수와 static 함수는, 이 함수들을 포함하는 모든 파일에 정의될 수 있다는 공통점이 있습니다.

 

위의 문장을 좀 더 쉽게 이해할 수 있도록, 다음글을 먼저 읽어보는 것을 추천합니다.

 

[C++] inline 함수에 대한 이해

함수 호출과 inline 확장( expansion )아래의 예문은 main 함수에서 간단한 함수를 호출하는 코드를 보여줍니다.#include int add( int a, int b){ return a + b;}int main(){ int val = add( 3, 5); // 함수 호출 std::cout 그런

codingbonfire.tistory.com

 

원래, 일반 함수가 서로 다른 두 파일에 정의되면, 프로그램 내에서 식별자의 정의는 하나만 존재해야 되다는 ODR( one definition rule )을 어기게 되어, 링크 오류가 발생됩니다.

// functions.cpp ------------------------------------
int normal_func(int x){ // 일반 함수의 정의
    return x + 3;
}

// main.cpp -----------------------------------------
#include <iostream>

int normal_func(int x){ // 일반 함수 정의
    return x + 3;
}

int main(){
    
    int val = normal_func(4);
    std::cout << val << '\n';
}

▼출력

multiple definition of `normal_func(int)'

이런 오류가 발생하는 이유는, 이전 글에서 말했듯이, 함수는 기본값으로 외부 링크( external linkage ) 속성을 갖고 있기 때문입니다.

그래서, 프로그램 내에 하나의 정의( definition )만을 가져야 합니다.

그런데, 위의 normal_func는 "functions.cpp" 파일과 "main.cpp" 함수 두 군데에서 정의되었기 때문에 오류가 발생한 것입니다.

 

하지만, inlinestatic 함수는 아래 예문처럼 두 파일 모두에 정의되는 것이 가능합니다.

// functions.cpp --------------------------------------
inline int inline_func(int x){  // inline 함수 정의
    return x + 3;
}

static int static_func(int x){  // statuc 함수 정의
    return x + 3;
}

// main.cpp -------------------------------------------
#include <iostream>

inline int inline_func(int x){  // inline 함수 정의
    return x + 3;
}

static int static_func(int x){  // statuc 함수 정의
    return x + 3;
}

int main(){
    
    int val = inline_func(4);
    val = static_func(5);
    std::cout << val << '\n';
}

▼출력

8

그리고, 파일에서 함수가 정의된다는 것은, 컴파일러가 이 파일을 컴파일할 때, 가능한 경우 inline 확장( expansion )과 같은 최적화를 수행할 수 있다는 얘기가 됩니다.

inline 확장은 컴파일러가 함수 호출의 코드를 함수의 구현 코드로 대체하는 것을 말하며, 이것은 함수의 정의가 파일의 외부에 있는 경우에는 불가능합니다. ( 컴파일러가 이런 함수의 구현 코드를 알 수 없으므로 )

 

그럼, 다른 두 키워드( inlinestatic )를 사용하여, 두 함수를 구분하는 것은 왜 그런 걸까요?

 

두 함수의 차이점

두 함수는 함수의 링크( linkage ) 속성이 서로 다릅니다.

 

먼저, static 함수는 내부 링크( internal linkage )를 갖고 있습니다.

그렇다는 것은, static 함수는 이 함수가 정의되어 있는 파일에서만 접근할 수 있고, 다른 파일에선 이 함수에 접근할 수 없다는 것을 말합니다.

 

그렇기 때문에, 위 예문에서, 두 파일에 정의되어 static 함수는 서로 독립적인 관계를 갖게 됩니다.

즉, "functions.cpp"에 정의된 함수와 "main.cpp" 파일에 정의된 static_func 함수는 이름만 같은 뿐, 사실상 서로 다른 함수로 취급됩니다.

그래서, 아래와 같이 함수의 내용을 서로 다르게 할 수도 있습니다.

// functions.cpp ---------------------------------------
static int static_func(int x){  // statuc 함수 정의
    return x + 5;
}

// main.cpp --------------------------------------------
static int static_func(int x){  // statuc 함수 정의
    return x + 3;
}

하지만, 이렇게 이름만 같고, 내용은 다른 함수를 구현하는 것의 이익을 찾아보기 힘듭니다.

게다가, 이 static_func 함수를 헤더 파일에 정의하여, 이 헤더 파일을 10개의 다른 코드 파일(. cpp)에서 포함( #include )했다고 해 봅시다.

이 경우, 프로그램에는 같은 기능의 static_func10개 생기게 됩니다.

 

한편, inline 함수는 외부 링크( external linkage )를 갖고 있습니다. ( static 함수를 제외한 함수는 기본값으로 외부 링크를 갖습니다. )

그래서, 이 함수는 프로그램 내의 모든 파일에서 접근할 수 있습니다.

그런데, 이 함수를 위의 static 함수의 경우와 같이 헤더 파일에 정의하여, 이 헤더 파일을 다른 두 개 이상의 파일에서 포함한다고 하면, 이번에 링크 오류를 만나게 됩니다.

이것은 프로그램 내 하나의 정의만 있어야 한다는 ODR( one definition rule )을 어기는 것이 되기 때문입니다.

 

따라서, C++에서는 inline 함수를 ODR의 예외로 두었습니다.

그리고, 이것이 가능하도록, 링커( linker )가 오브젝트( object ) 파일들을 링크하는 과정에서 inline 함수 정의의 중복을 제거하도록 만들었습니다.

그래서, inline 함수는 프로그램 내에 하나의 정의만 갖게 됨에도 불구하고, 컴파일 시에, 이 함수에 접근을 원하는 모든 파일에서 이 함수의 정의를 알 수 있게 된 것입니다.

 

그리고, 위에서 얘기했듯이, 함수의 정의를 안다는 것은 최적화의 가능성을 가져올 수 있습니다.

여기서 주의할 것은, inline 함수는 각각의 정의가 동일해야 한다는 것입니다.

// functions.cpp ---------------------------------------
inline int inline_func(int x){  // inline 함수 정의
    return x + 3;
}

// main.cpp --------------------------------------------
inline int inline_func(int x){  // inline 함수 정의
    return x + 3;	// 위의 정의와 동일해야 합니다.
}

이것은 링크 과정에서 inline 함수의 중복되는 정의가 삭제되는데, 만약 함수의 내용이 다르다면, 삭제 순서에 따라 남은 inline 함수가 어떤 동작 ( undefined behavior )을 할지 모르기 때문입니다.

 

두 함수의 링크( linkage ) 확인

위에서, static 함수는 파일 별로 독립적인 함수로 정의되고, inline 함수는 프로그램 내 하나의 정의만을 가진다고 했습니다.

그 말을 확인하기 위해서, 두 종류의 함수의 주소를 출력해 볼 것입니다.

// functions.cpp
#include <iostream>

inline int inline_func(int x){  // inline 함수 정의
    return x + 3;
}

static int static_func(int x){  // statuc 함수 정의
    return x + 5;
}

// functions.cpp 파일에 정의된 함수들의 주소 출력
void print_address_1(){

    std::cout << "static func address 1: " << (void*)static_func << '\n';
    std::cout << "inline func address 1: " << (void*)inline_func << '\n';
}

위의 print_address_1 함수는 "functions.cpp" 파일에 정의된 두 staticinline 함수의 주소를 출력합니다.

 

그리고, 다음엔 print_address_2 함수를 사용하여 "main.cpp" 파일에 정의된 두 함수의 주소를 출력할 것입니다.

// main.cpp
#include <iostream>

inline int inline_func(int x){  // inline 함수 정의
    return x + 3;
}

static int static_func(int x){  // statuc 함수 정의
    return x + 3;
}

// print_address_1 함수 호출을 위한 전방 선언
void print_address_1();  

void print_address_2(){

    std::cout << "static func address 2: " << (void*)static_func << '\n';
    std::cout << "inline func address 2: " << (void*)inline_func << '\n';
}

int main(){

    print_address_1();	// function.cpp에 정의된 함수 주소 출력
    
    std::cout << '\n';
    print_address_2();	// main.cpp에 정의된 함수 주소 출력
}

▼출력

static func address 1: 0x7ff6de5f16f3
inline func address 1: 0x7ff6de5f3400

static func address 2: 0x7ff6de5f17b4
inline func address 2: 0x7ff6de5f3400

위에서 출력한 함수의 주소는 당연히 실행할 때마다 다를 수 있지만, 여기서 확인할 수 있는 것은 static 함수는 두 함수의 주소가 다르다는 것입니다.

이것은, 이 두 함수가 같은 함수가 아니라는 것을 말합니다.

 

반대로, inline 함수는 같은 주소를 갖고 있는 것을 볼 수 있습니다.

즉, 프로그램 내에 inline 함수의 정의가 하나뿐이라는 것을 알 수 있습니다.

그리고, 이것은 링크 과정에서 중복된 정의의 삭제를 통한 결과입니다.

 

참고로, 위 예문의 << 연산자는 std::cout 객체를 통해서 데이터를 출력할 때, 데이터의 타입에 따라 그에 맞는 내용을 출력하도록 오버로딩( overloading )되어 있습니다.

그래서, 함수의 주소나 다른 식별자의 주소를 출력하려면 데이터의 타입이 없음을 뜻하는 void*를 사용하며 데이터를 출력해야 됩니다.

 

 

정리

  • static 함수는 이 함수를 사용하는 파일마다 하나의 독립된 정의를 갖습니다.
  • inlilne 함수는 프로그램 전체에 하나뿐인 정의를 갖습니다.
  • 두 함수 모두 헤더 파일에 정의하여, 이를 사용하는 파일에 전파될 수 있습니다.

 

 

이 글과 관련 있는 글들

 

 

 

 

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유
  • 카카오스토리 공유