ネストされた関数は標準Cの一部ではないことは知っていますが、それらはgccに存在するため(そしてgccが私が気にする唯一のコンパイラーであるという事実)、私はそれらを頻繁に使用する傾向があります。
これは悪いことですか?もしそうなら、私にいくつかの厄介な例を見せてもらえますか?gccのネストされた関数のステータスはどうなっていますか?それらは削除されますか?
ネストされた関数は標準Cの一部ではないことは知っていますが、それらはgccに存在するため(そしてgccが私が気にする唯一のコンパイラーであるという事実)、私はそれらを頻繁に使用する傾向があります。
これは悪いことですか?もしそうなら、私にいくつかの厄介な例を見せてもらえますか?gccのネストされた関数のステータスはどうなっていますか?それらは削除されますか?
ネストされた関数は、ネストされていない関数ではできないことを実際には何もしません (これが、C も C++ も提供していない理由です)。あなたは、他のコンパイラには興味がないと言っています - 現時点ではこれは真実かもしれませんが、将来何が起こるかは誰にもわかりません。他のすべての GCC の「機能強化」と同様に、それらは避けます。
これを説明するための小さな話 - 私は主に DEC ボックス - 具体的には DEC-10 といくつかの VAXen を使用していた UK Polytechinc で働いていました。すべての工学部は、FORTRAN に対する多くの DEC 拡張機能をコードで使用していました。そして、DEC-10 を IBM メインフレームに置き換えましたが、その FORTRAN コンパイラはどの拡張機能もサポートしていませんでした。その日は多くの嘆きと歯ぎしりがあったと言えます。私自身の FORTRAN コード (8080 シミュレーター) は、2 時間ほどで IBM に移植されました (ほぼすべてが IBM コンパイラーの操作方法の学習に費やされました)。
入れ子関数は、特に多くの変数をシャッフルするアルゴリズムで役立つ場合があります。書き出された4方向マージソートのようなものは、多くのローカル変数を保持する必要があり、それらの多くを使用する多数の繰り返しコードが必要になる可能性があります。繰り返されるコードのこれらのビットを外部ヘルパールーチンとして呼び出すには、多数のパラメーターを渡すか、ヘルパールーチンに別のレベルのポインター間接参照を介してそれらにアクセスさせる必要があります。
このような状況では、ネストされたルーチンは、コードを記述する他の手段よりも効率的なプログラム実行を可能にする可能性があると想像できます。少なくとも、存在する再帰が最も外側の関数を呼び出すことによって行われる状況に合わせてコンパイラーが最適化する場合です。キャッシュされていないCPUでは、スペースが許す限りインライン関数の方が適している場合がありますが、個別のルーチンを使用することで提供されるよりコンパクトなコードが役立つ場合があります。内部関数がそれ自体または相互に再帰的に呼び出すことができない場合、それらはスタックフレームを外部関数と共有できるため、余分なポインター逆参照の時間ペナルティなしにその変数にアクセスできます。
そうは言っても、他の方法でコードを書き直さなければならないことから生じる可能性のある将来のコストを当面の利益が上回る状況を除いて、コンパイラ固有の機能の使用は避けます。
ほとんどのプログラミング手法と同様に、ネストされた関数は、適切な場合にのみ使用する必要があります。
この側面を使用する必要はありませんが、必要に応じて、ネストされた関数は、含まれている関数のローカル変数に直接アクセスすることにより、パラメーターを渡す必要性を減らします。便利です。「見えない」パラメータを注意深く使用すると、読みやすさが向上します。不注意に使用すると、コードがはるかに不透明になる可能性があります。
一部またはすべてのパラメーターを回避すると、新しい包含関数で同じ変数を宣言する必要があるため、ネストされた関数を他の場所で再利用することが難しくなります。通常、再利用は適切ですが、多くの関数は再利用されないため、問題にならないことがよくあります。
変数の型はその名前とともに継承されるため、ネストされた関数を再利用すると、限定されたプリミティブバージョンのテンプレートのように、安価なポリモーフィズムを実現できます。
ネストされた関数を使用すると、関数が意図せずにコンテナーの変数の1つにアクセスしたり、変更したりした場合にも、バグの危険性が生じます。ローカル宣言なしで同じインデックスを使用するforループを含むネストされた関数への呼び出しを含むforループを想像してみてください。言語を設計する場合、ネストされた関数を含めますが、何が起こっているのかをより明確にし、意図しない継承や変更を避けるために、「inheritx」または「inheritconstx」宣言が必要です。
他にもいくつかの用途がありますが、ネストされた関数が行う最も重要なことは、外部からは見えない内部ヘルパー関数、CおよびC++の静的非外部関数またはC++のプライベート非パブリック関数への拡張を許可することです。カプセル化のレベルが2つある方が、1つよりも優れています。また、関数名のローカルオーバーロードが可能になるため、それぞれが機能するタイプを説明する長い名前は必要ありません。
包含関数が包含関数へのポインターを格納する場合、および複数レベルのネストが許可される場合、内部的な問題が発生しますが、コンパイラーの作成者は半世紀以上にわたってこれらの問題に取り組んできました。CよりもC++に追加するのを難しくする技術的な問題はありませんが、利点は少なくなります。
移植性は重要ですが、gccは多くの環境で使用可能であり、少なくとも1つのコンパイラーファミリーがネストされた機能をサポートします。IBMのxlcはAIX、Linux on PowerPC、Linux on BlueGene、Linux on Cell、およびz/OSで使用できます。http://publib.boulder.ibm.com/infocenter/comphelp/v8v101index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fnested_functions.htmを参照してください 。
ネストされた関数は、Ada、Pascal、Fortran、PL / I、PL / IX、Algol、COBOLなど、いくつかの新しい言語(Pythonなど)およびより多くの従来の言語で使用できます。C ++には2つの制限されたバージョンもあります。ローカルクラスのメソッドは、それを含む関数の静的(自動ではない)変数にアクセスでき、任意のクラスのメソッドは、静的クラスのデータメンバーとメソッドにアクセスできます。今後のC++標準には、実際には匿名のネストされた関数であるlamda関数があります。ですから、プログラミングの世界には、彼らとの賛否両論の経験がたくさんあります。
ネストされた関数は便利ですが、注意してください。傷ついた場所ではなく、役立つ場所で常に機能やツールを使用してください。
あなたが言ったように、それらはC標準の一部ではないという意味で悪いことであり、他の多くの(どれか?) Cコンパイラによって実装されていません。
また、g++ はネストされた関数を実装していないため、そのコードの一部を取得して C++ プログラムにダンプする必要がある場合は、それらを削除する必要があることに注意してください。
特定の条件下で NX (非実行) セキュリティ ビットが無効になるため、ネストされた関数は不適切な場合があります。それらの条件は次のとおりです。
GCCとネストされた関数が使用されています
ネストされた関数へのポインタが使用されています
ネストされた関数は、親関数から変数にアクセスします
このアーキテクチャは、NX (非実行) ビット保護を提供します。たとえば、64 ビット Linux です。
上記の条件が満たされると、GCC はトランポリンhttps://gcc.gnu.org/onlinedocs/gccint/Trampolines.htmlを作成します。トランポリンをサポートするために、スタックは実行可能としてマークされます。参照: https://www.win.tue.nl/~aeb/linux/hh/protection.html
NX セキュリティ ビットを無効にすると、いくつかのセキュリティ問題が発生します。注目すべき問題は、バッファ オーバーラン保護が無効になることです。具体的には、攻撃者が何らかのコードをスタック (ユーザーが設定可能な画像、配列、または文字列の一部など) に配置し、バッファ オーバーランが発生した場合、攻撃者のコードが実行される可能性があります。
私はStefanの例に同意します.ネストされた関数を使用した(そしてそれらを宣言しているinline
)のは、同様の機会だけです。
また、ネストされたインライン関数をめったに使用しないことをお勧めします。また、それらを使用する回数が少ない場合は、それらを取り除くための戦略を (心の中で、コメントで) 持つ必要があります (おそらく、条件付き#ifdef __GCC__
コンパイルで実装することもできます)。
しかし、GCC は無料の (スピーチのように) コンパイラであるため、いくつかの違いがあります...そして、一部の GCC 拡張機能は事実上の標準になる傾向があり、他のコンパイラによって実装されています。
私が非常に便利だと思うもう 1 つの GCC 拡張機能は、計算された goto、つまりlabel as valuesです。オートマトンやバイトコード インタープリターをコーディングする場合、非常に便利です。
ネストされた関数を使用すると、多くのグローバル状態を導入することなく、明示的なパラメーターの受け渡しの量を減らすことで、プログラムを読みやすく、理解しやすくすることができます。
一方、他のコンパイラには移植できません。(デバイスではなくコンパイラに注意してください。gccが実行されない場所は多くありません)。
したがって、ネストされた関数を使用してプログラムをより明確にすることができる場所を見つけた場合は、「移植性または読みやすさを最適化していますか」と自問する必要があります。
ネストされた関数の少し異なる種類の使用法を調査しているところです。C における「遅延評価」のアプローチとして。
そのようなコードを想像してください:
void vars()
{
bool b0 = code0; // do something expensive or to ugly to put into if statement
bool b1 = code1;
if (b0) do_something0();
else if (b1) do_something1();
}
対
void funcs()
{
bool b0() { return code0; }
bool b1() { return code1; }
if (b0()) do_something0();
else if (b1()) do_something1();
}
このようにして、必要な場合にのみコードが実行される一方で、明確になります (そのようなコードを初めて見たときは少し混乱するかもしれません)。同時に、元のバージョンに戻すのは非常に簡単です。
ここで、同じ「値」が複数回使用されると、1 つの問題が発生します。コンパイル時にすべての値がわかっている場合、GCCは単一の「呼び出し」に最適化できましたが、それは重要な関数呼び出しなどでは機能しないと思います。この場合、「キャッシング」を使用できますが、読みにくくなります。
ネストされた関数は、本格的なプログラミング言語では必須です。
それらがなければ、実際の機能感覚は使えません。
これは、字句スコープと呼ばれます。