ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 인라인 함수
    Programming/C++ 언어 2008. 8. 20. 09:10

    16-3.인라인 함수

    이 절 이후의 인라인 함수, 디폴트 인수, 오버로딩은 C++에서 새로 추가된 기능들이다. 따라서 C 컴파일러에서는 컴파일되지 않는다.

    16-3-가.인라인 함수

    함수는 반복된 동작을 정의함으로써 프로그램의 기본 부품을 구성하는 단위가 된다. 입력과 출력, 내부 동작을 한 번만 잘 작성해 놓으면 필요할 때마다 불러서 똑같은(또는 비슷한) 작업을 여러 번 수행할 수 있다. 다음 예제는 인수로 전달된 n보다 작은 정수 난수를 하나 생성한 후 돌려 주는 randfunc라는 함수를 정의하고 main에서 이 함수를 세 번 호출한다.

     

    : randfunc

    #include <Turboc.h>

     

    int randfunc(int n)

    {

         return rand()%n;

    }

     

    void main()

    {

         int i,j,k;

     

         i=randfunc(10);

         j=randfunc(100);

         k=randfunc(50);

         printf("난수=%d,%d,%d\n",i,j,k);

    }

     

    매번 범위를 다르게 하여 호출했는데 실행 결과는 "난수=1,67,34"과 같으며 컴파일러에 따라 조금씩 달라질 수 있다. 이 프로그램의 동작을 그림으로 그려 보면 다음과 같다.

    main에서 randfunc 함수를 호출할 때 정수 인수 하나를 전달하는데 이때 randfunc 함수로 분기가 일어나며 randfunc 함수는 인수 범위까지의 난수를 생성한 후 호출원으로 리턴한다. 매번 randfunc 함수를 호출할 때마다 이런 과정이 반복되는데 함수가 한 번 호출될 때 내부적으로 어떤 일들이 벌어지는지 살펴보자. 앞에서 이미 알아본 내용들인데 다시 한 번 더 정리해 보았다.

     

    인수를 전달하기 위해 인수값을 순서대로 스택에 밀어 넣는다.

    호출원은 바로 다음 번지를 스택에 기록함으로써 함수가 복귀할 번지를 저장한다.

    함수가 정의되어 있는 번지로 점프하여 제어권을 함수에게 넘긴다.

    함수는 스택에 자신의 지역변수를 위한 공간을 만든다.

    함수의 코드를 수행한다.

    리턴값을 넘긴다.

    복귀 번지로 리턴한다.

    ⑧ 인수 전달에 사용한 스택을 정리한다.

     

    함수가 호출되고 복귀되는 과정은 이렇게 복잡하다. 물론 동작에 필요한 이런 코드들은 컴파일러가 작성해 주므로 우리는 단순히 이름과 인수만으로 함수를 호출할 수 있다. 함수의 동작이 복잡하고 길이가 길다면 이 정도 호출 시간은 얼마든지 무시할 수 있을 것이다. 하지만 rand()%n이라는 간단한 연산을 위해 이런 복잡한 호출 절차를 거쳐야 한다는 것은 큰 부담이 아닐 수 없는데 함수의 실행에 걸리는 시간보다 호출에 걸리는 시간의 비율이 너무 크기 때문이다.

    함수의 실행 시간은 0.001초밖에 안걸리는데 함수를 호출하는 시간이 0.1초나 걸린다면 이럴 때는 이 코드를 함수로 만들지 않고 호출부에 바로 삽입하는 것이 훨씬 더 이득이다. 이런 개념이 바로 인라인(inline) 함수이다. 인라인 함수는 함수이기는 하되 호출될 때 함수가 있는 곳으로 점프하지 않고 함수의 본체 코드를 호출부 자리에 바로 삽입하는 방식의 함수이다. 위 예제의 randfunc 함수를 인라인으로 바꿔 보자. 함수 정의부 앞에 inline이라는 키워드만 붙이면 된다.

     

    inline int randfunc(int n)

    {

         return rand()%n;

    }

     

    이렇게 수정하면 randfunc 함수는 인라인 함수가 된다. 실행 파일에는 main 함수만 있고 인라인 함수는 따로 작성되지 않으며 대신 인라인 함수가 호출될 때마다 이 함수의 본체가 호출부에 삽입된다.

    컴파일러는 randfunc 함수의 본체 코드 rand()%n을 기억하고 있다가 인라인 함수가 호출되는 곳에 이 코드를 바로 삽입한다. i=randfunc(10) 호출문에서 randfunc(10)이 rand()%10으로 대체되어 버리는 것이다. 이어지는 j=randfunc(100), k=randfunc(50) 호출문도 마찬가지 방식으로 처리된다.

    인라인 함수는 실제로 호출되지 않으므로 호출에 걸리는 시간이 필요없어 전체적인 실행 속도가 대단히 빠르다. 복귀 번지를 기록하거나 인수, 리턴값을 전달하는 시간만큼을 절약할 수 있다. 대신 인라인 함수가 호출되는 곳마다 함수의 본체가 삽입되므로 실행 파일의 크기가 커지는 단점이 있다. 즉, 인라인 함수는 속도에 유리하고 크기에 불리한 방법이다.

    본체 코드가 아주 작고 속도가 중요할 때 인라인 함수를 사용한다. 함수의 본체가 길고 동작이 복잡하다면 이런 함수는 인라인으로 만들지 않는 것이 더 좋다. 긴 함수는 호출에 걸리는 시간의 비율이 함수의 실행 시간에 비해 아주 작아서 호출 부담이 크지 않으며 또한 장문의 코드를 매번 반복한다면 실행 파일의 크기가 무시못할 정도로 커져 버리기 때문이다.

    함수를 인라인으로 만들 때는 함수의 원형이나 정의부에 inline 키워드만 써 주면 된다. 또는 양쪽에 다 inline을 표기해도 상관없다. 보통 인라인 함수는 길이가 짧기 때문에 별도로 원형 선언을 하지 않고 원형이 들어갈 자리에 inline 키워드와 함께 본체를 같이 정의하는 것이 일반적이다. 앞 예제에서 작성한 randfunc 함수가 바로 이 방식으로 작성되었다. 여러 모듈에서 공유하는 함수라면 헤더 파일에 작성해야 한다. 인라인 함수의 본체는 정의가 아닌 선언이므로 메모리를 소모하지도 않으며 중복 선언해도 상관없으므로 보통 헤더 파일에 작성한다. 단 같은 모듈에서 두 번 선언하는 것은 안된다.

    함수가 인라인이 될 것인가 아닌가는 프로그래머가 지정하지만 최종 결정은 컴파일러가 한다. 프로그래머가 함수 선언앞에 inline 키워드를 붙이더라도 컴파일러는 이 지정을 무시하고 일반 함수로 만들어 버릴 수도 있다. 프로그래머가 inline 키워드를 사용하는 것은 이 함수가 인라인이 되었으면 좋겠다는 희망 사항일 뿐이며 컴파일러는 조건이 맞지 않을 경우 이 지정을 무시할 수 있다. 마치 register 기억 부류와 유사하다.

    예를 들어 재귀 호출 함수는 인라인이 될 수 없다. 왜냐하면 재귀 호출이란 스택을 기반으로 동작하기 때문에 실제로 호출되어야만 자기 자신을 호출할 수 있다. 만약 인라인 함수가 재귀 호출을 하도록 내버려 둔다면 이 프로그램의 크기는 무한대가 되어 버릴 것이다. 프로그램의 다른 곳에서 이 함수의 주소를 참조하는 경우가 있다면 이 경우도 인라인 함수가 되지 못한다. 인라인 함수는 번지를 가질 수 없다.

    또한 함수의 길이가 너무 길 경우도 득보다 실이 더 많기 때문에 컴파일러는 이 함수를 강제로 일반 함수로 만들어 버린다. 비주얼 C++의 경우 디버그 버전에서는 디버깅의 편의를 위해 모든 인라인 함수를 일반 함수로 컴파일하는데 프로젝트 옵션으로 이를 조정할 수 있다. 프로젝트 설정 대화상자의 C/C++탭의 Optimizations 탭에 보면 인라인 함수 확장에 대한 옵션이 있다.

    반대로 inline 키워드를 명시적으로 쓰지 않아도 자동으로 인라인 함수가 되는 경우도 있는데 클래스 선언에 코드가 작성되어 있는 멤버 함수는 자동 인라인 속성을 가진다. 함수가 인라인이 될 것인가 아닌가는 어디까지나 속도와 크기의 차이가 있을 뿐이지 프로그램의 동작 자체가 달라지는 것은 아니므로 컴파일러가 재량껏 inline 지정을 무시하거나 강제 지정하더라도 별 상관은 없다. 프로그래머는 다만 인라인으로 만들고 싶은 함수 앞에 inline 키워드만 써 주면 된다.

     [원본] http://www.winapi.co.kr/clec/cpp2/16-3-1.htm

    댓글

Designed by Tistory.