inline int factorial(int n)
{
if(!n) return 1;
else return n*factorial(n-1);
}
これを読んでいると、コンパイラによって正しく処理されない場合、上記のコードが「無限コンパイル」につながることがわかりました。
コンパイラは関数をインライン化するかどうかをどのように決定しますか?
inline int factorial(int n)
{
if(!n) return 1;
else return n*factorial(n-1);
}
これを読んでいると、コンパイラによって正しく処理されない場合、上記のコードが「無限コンパイル」につながることがわかりました。
コンパイラは関数をインライン化するかどうかをどのように決定しますか?
まず、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 だと思います)。
実際、コンパイラが賢く動作しない場合、inline
d 関数のコピーを再帰的に挿入しようとし、無限に大きなコードを作成する可能性があります。ただし、最近のほとんどのコンパイラはこれを認識します。次のいずれかを実行できます。
ケース 2 の場合、多くのコンパイラには、#pragma
これを実行する最大の深さを指定するために設定できる があります。gccでは、コマンドラインからこれを渡すこともできます--max-inline-insns-recursive
(詳細はこちらを参照)。
AFAIK GCCは、可能であれば、再帰関数でテールコールの削除を行います。ただし、関数は末尾再帰ではありません。
コンパイラはコール グラフを作成します。それ自体を呼び出しているサイクルが検出されると、関数は特定の深さ (n=1、10、100、コンパイラが調整されているものは何でも) の後にインライン化されなくなります。
一部の再帰関数は、効果的に無限にインライン展開するループに変換できます。gcc でこれができると思いますが、他のコンパイラについては知りません。
これが通常うまくいかない理由については、すでに与えられた回答を参照してください。
「脚注」として、テンプレートメタプログラミングを使用して、探している効果を(少なくとも例として使用している階乗について)達成できます。ウィキペディアからの貼り付け:
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};
コンパイラは、これらの種類のものを検出して防止するためにコール グラフを作成します。したがって、関数がインラインではなく、それ自体を呼び出していることがわかります。
ただし、主に inline キーワードとコンパイラ スイッチによって制御されます (たとえば、キーワードがなくても小さな関数を自動インライン化することができます)。コールスタックがミラーに保存されないため、デバッグ コンパイルは決してインライン化しないことに注意することが重要です。コードで作成した呼び出し。
「コンパイラは関数をインライン化するかどうかをどのように決定しますか?」
これは、コンパイラ、指定されたオプション、コンパイラのバージョン番号、使用可能なメモリの量などによって異なります。
プログラムのソース コードは、インライン関数の規則に従う必要があります。関数がインライン化されるかどうかにかかわらず、インライン化される可能性に備える必要があります (未知の回数)。
再帰マクロは通常違法であるというウィキペディアの声明は、かなり情報が不足しているように見えます。C と C++ は再帰的な呼び出しを防ぎますが、翻訳単位が再帰的であるように見えるマクロ コードを含むことによって不正になることはありません。アセンブラでは、通常、再帰マクロは有効です。
一部のコンパイラ (Borland C++ など) は、条件ステートメント (if、case、while など) を含むコードをインライン化しないため、例の再帰関数はインライン化されません。