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

これを読んでいると、コンパイラによって正しく処理されない場合、上記のコードが「無限コンパイル」につながることがわかりました。

コンパイラは関数をインライン化するかどうかをどのように決定しますか?

4

9 に答える 9

151

まず、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 だと思います)。

于 2008-10-10T05:47:13.117 に答える
25

実際、コンパイラが賢く動作しない場合、inlined 関数のコピーを再帰的に挿入しようとし、無限に大きなコードを作成する可能性があります。ただし、最近のほとんどのコンパイラはこれを認識します。次のいずれかを実行できます。

  1. 関数をまったくインライン化しない
  2. 特定の深さまでインライン化し、それまでに終了していない場合は、標準の関数呼び出し規則を使用して関数の別のインスタンスを呼び出します。これにより、多くの一般的なケースを高性能な方法で処理できますが、呼び出しの深さが大きいまれなケースのフォールバックを残します。これは、その関数のコードのインライン バージョンと個別バージョンの両方を維持することも意味します。

ケース 2 の場合、多くのコンパイラには、#pragmaこれを実行する最大の深さを指定するために設定できる があります。gccでは、コマンドラインからこれを渡すこともできます--max-inline-insns-recursive(詳細はこちらを参照)。

于 2008-10-10T05:42:29.803 に答える
8

AFAIK GCCは、可能であれば、再帰関数でテールコールの削除を行います。ただし、関数は末尾再帰ではありません。

于 2008-10-10T05:56:47.793 に答える
6

コンパイラはコール グラフを作成します。それ自体を呼び出しているサイクルが検出されると、関数は特定の深さ (n=1、10、100、コンパイラが調整されているものは何でも) の後にインライン化されなくなります。

于 2008-10-10T05:45:12.527 に答える
3

一部の再帰関数は、効果的に無限にインライン展開するループに変換できます。gcc でこれができると思いますが、他のコンパイラについては知りません。

于 2008-10-16T19:53:55.420 に答える
3

これが通常うまくいかない理由については、すでに与えられた回答を参照してください。

「脚注」として、テンプレートメタプログラミングを使用して、探している効果を(少なくとも例として使用している階乗について)達成できます。ウィキペディアからの貼り付け:

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

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};
于 2008-10-10T05:50:24.920 に答える
1

コンパイラは、これらの種類のものを検出して防止するためにコール グラフを作成します。したがって、関数がインラインではなく、それ自体を呼び出していることがわかります。

ただし、主に inline キーワードとコンパイラ スイッチによって制御されます (たとえば、キーワードがなくても小さな関数を自動インライン化することができます)。コールスタックがミラーに保存されないため、デバッグ コンパイルは決してインライン化しないことに注意することが重要です。コードで作成した呼び出し。

于 2008-10-10T05:36:45.633 に答える
1

「コンパイラは関数をインライン化するかどうかをどのように決定しますか?」

これは、コンパイラ、指定されたオプション、コンパイラのバージョン番号、使用可能なメモリの量などによって異なります。

プログラムのソース コードは、インライン関数の規則に従う必要があります。関数がインライン化されるかどうかにかかわらず、インライン化される可能性に備える必要があります (未知の回数)。

再帰マクロは通常違法であるというウィキペディアの声明は、かなり情報が不足しているように見えます。C と C++ は再帰的な呼び出しを防ぎますが、翻訳単位が再帰的であるように見えるマクロ コードを含むことによって不正になることはありません。アセンブラでは、通常、再帰マクロは有効です。

于 2008-10-10T05:50:15.230 に答える
0

一部のコンパイラ (Borland C++ など) は、条件ステートメント (if、case、while など) を含むコードをインライン化しないため、例の再帰関数はインライン化されません。

于 2008-10-17T05:08:58.573 に答える