재귀 함수가 인라인 일 수 있습니까?


134
inline int factorial(int n)
{
    if(!n) return 1;
    else return n*factorial(n-1);
}

내가 이것을 읽었을 때 컴파일러가 올바르게 처리하지 않으면 위의 코드가 "무한 컴파일"로 이어질 것이라는 것을 알았습니다.

컴파일러는 함수를 인라인할지 여부를 어떻게 결정합니까?

답변:


137

첫째, inline함수 의 스펙은 힌트 일뿐입니다. 컴파일러는 inline한정자의 유무를 완전히 무시할 수 있으며 종종 무시합니다 . 즉, 상기와 더불어, 컴파일러 는 무한 루프 언 롤링 수만큼 재귀 함수를 인라인. 단순히 기능을 "롤"할 수있는 수준에 제한을 두어야합니다.

최적화 컴파일러는 다음 코드를 사용할 수 있습니다.

inline int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    return factorial(x);
}

이 코드로 :

int factorial(int n)
{
    if (n <= 1)
    {
        return 1;
    }
    else
    {
        return n * factorial(n - 1);
    }
}

int f(int x)
{
    if (x <= 1)
    {
        return 1;
    }
    else
    {
        int x2 = x - 1;
        if (x2 <= 1)
        {
            return x * 1;
        }
        else
        {
            int x3 = x2 - 1;
            if (x3 <= 1)
            {
                return x * x2 * 1;
            }
            else
            {
                return x * x2 * x3 * factorial(x3 - 1);
            }
        }
    }
}

이 경우 기본적으로 함수를 3 번 ​​인라인했습니다. 일부 컴파일러 이 최적화를 수행합니다. MSVC ++는 재귀 함수 (최대 20 개)에서 수행 될 인라인 레벨을 조정하는 설정을 가지고 있습니다.


20
#pragma inline_recursion (on)입니다. 최대 깊이에 대한 문서가 일관성이 없거나 결정적이지 않습니다. 값 8, 16 또는 #pragma inline_depth 값이 가능합니다.
peterchen

@ peterchen 인라인 된 함수가 인수 중 하나의 값을 변경하는 경우 main 대신 함수 내부의 함수를 인라인하는 것이 좋습니다. 내 영어를 유감스럽게 생각합니다
ob_dev 7:24의

1
@obounaim : 당신은 그렇게 생각할 것입니다. MSVC는 그렇지 않습니다.
SecurityMatt

23

실제로 컴파일러가 지능적으로 작동하지 않으면 inlined 함수의 사본을 재귀 적으로 삽입 하여 무한히 큰 코드를 작성 하려고 할 수 있습니다 . 그러나 대부분의 최신 컴파일러는이를 인식합니다. 그들은 다음 중 하나를 수행 할 수 있습니다.

  1. 전혀 함수를 인라인하지 않습니다
  2. 특정 깊이까지 인라인하고 종료되지 않은 경우 표준 함수 호출 규칙을 사용하여 함수의 개별 인스턴스를 호출하십시오. 이렇게하면 많은 일반적인 경우를 고성능 방식으로 처리 할 수 ​​있으며 통화 깊이가 큰 드문 경우에는 대체 할 수 있습니다. 이것은 또한 함수 코드의 인라인 버전과 별도 버전을 유지한다는 것을 의미합니다.

사례 2의 경우 많은 컴파일러 #pragma에서이 작업을 수행 할 최대 깊이를 지정하도록 설정할 수 있습니다. gcc 에서는 명령 행에서이 정보를 전달할 수도 있습니다 --max-inline-insns-recursive(자세한 내용은 여기 참조 ).


7

AFAIK GCC는 가능한 경우 재귀 함수에서 테일 콜 제거를 수행합니다. 그러나 함수는 꼬리 재귀가 아닙니다.


6

컴파일러는 호출 그래프를 만듭니다. 사이클 자체가 호출되는 것을 감지하면 함수는 더 이상 특정 깊이 (n = 1, 10, 100, 컴파일러가 조정 된대로) 후에 인라인되지 않습니다.


3

일부 재귀 함수는 루프로 변환되어 효과적으로 무한히 인라인됩니다. 나는 gcc가 이것을 할 수 있다고 생각하지만 다른 컴파일러에 대해서는 모른다.


2

이것이 일반적으로 작동하지 않는 이유는 이미 제공된 답변을 참조하십시오.

"각주"로서 템플릿 메타 프로그래밍을 사용하여 찾고있는 효과를 얻을 수 있습니다 (적어도 예제로 사용중인 계승에 대해서는) . Wikipedia에서 붙여 넣기 :

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

1
매우 귀엽지 만 원래 게시물에는 가변 인수 "int n"이 있습니다.
Windows 프로그래머

1
사실, 그러나 컴파일 타임에 n이 알려지지 않은 경우 "재귀 적 인라이닝"을 원한다는 점은 거의 없습니다 ... 컴파일러가 어떻게 그것을 달성 할 수 있을까요? 그래서 질문의 맥락에서 이것이 적절한 대안이라고 생각합니다.
yungchin

1
방법은 Derek Park의 예를 참조하십시오. 두 번 인라인하면 n >> 2 번 반복되고 결과 코드에서 2 + 2 개의 반환 값이 있습니다.
MSalters

1

컴파일러는 이러한 종류의 것들을 감지하고 방지하기 위해 호출 그래프를 만듭니다. 따라서 함수가 자체적으로 호출되고 인라인이 아니라는 것을 알 수 있습니다.

그러나 주로 인라인 키워드와 컴파일러 스위치에 의해 제어됩니다 (예를 들어 키워드 없이도 작은 함수를 자동 인라인으로 만들 수 있습니다). 코드에서 생성 한 호출


1

"컴파일러는 함수를 인라인할지 여부를 어떻게 결정합니까?"

컴파일러, 지정된 옵션, 컴파일러의 버전 번호, 사용 가능한 메모리 양 등에 따라 다릅니다.

프로그램의 소스 코드는 여전히 인라인 함수에 대한 규칙을 따라야합니다. 함수가 인라인되는지 여부에 관계없이 함수가 인라인 될 가능성을 준비해야합니다 (일부 알려지지 않은 횟수).

재귀 매크로는 일반적으로 불법적 인 것으로 보이며 정보가 잘못되었습니다. C 및 C ++는 재귀 적 호출을 방지하지만 재귀적인 것처럼 보이는 매크로 코드를 포함하여 변환 단위는 불법이되지 않습니다. 어셈블러에서 재귀 매크로는 일반적으로 합법적입니다.


0

일부 컴파일러 (예 : Borland C ++)는 조건문 (if, case, while 등)이 포함 된 코드를 인라인하지 않으므로 예제의 재귀 함수가 인라인되지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.