web-dev-qa-db-ja.com

最新のC ++コンパイラは、特定の条件下でconst関数を2回呼び出すことを回避できますか?

たとえば、このコードがある場合:

_class SomeDataProcessor
{
public:
    bool calc(const SomeData & d1, const SomeData & d2) const;
private:
    //Some non-mutable, non-static member variables
}

SomeDataProcessor sdp;
SomeData data1;
SomeData data2;

someObscureFunction(sdp.calc(data1, data2),
                    sdp.calc(data1, data2));
_

潜在的に同等のコードを考えてみましょう:

_bool b = sdp.calc(data1, data2);
someObscureFunction(b,b);
_

これを有効にするには、calc()関数がいくつかの要件を満たしている必要があります。この例では、プロパティ__pure_const_formula__を呼び出します

__pure_const_formula__は:

  • メンバー、静的またはグローバル変数の状態を変更しない
  • __pure_const_formula__関数のみを呼び出す
  • たぶん私が考えていない他の条件

たとえば、乱数ジェネレーターを呼び出すと、これらの要件に適合しません。

コンパイラは、呼び出された関数を再帰的に掘る必要がある場合でも、最初のコードを2番目のコードに置き換えることができますか?最新のコンパイラはこれを実行できますか?

37
galinette

GCCには、関数にpureattribute__attribute__((pure))として使用)があり、冗長な呼び出しを削除できることをコンパイラーに伝えます。使用されていますstrlenに。

呼び出される関数がソース形式で利用できない可能性があり、オブジェクトファイル形式には関数が純粋であるかどうかに関するメタデータが含まれていないという事実を考慮して、これを自動的に行うコンパイラーは特に知りません。

46
Tamás Zahola

はい、絶対に。

コンパイラはこれを常に行うなど

たとえば、関数がすべてtrueを返し、その定義が呼び出し側でコンパイラに見える場合、関数呼び出し全体がおそらく省略され、結果は次のようになります。

someObscureFunction(true, true);

コンパイラが十分な情報を持っているプログラムは、非常に複雑な一連のタスクからおそらく1つまたは2つの命令まで「最適化」されます。さて、実際にメンバー変数を操作すると、オプティマイザーがある程度まで限界に達しますが、変数がprivateであり、既知の初期値が与えられ、他のメンバー関数によって変更されていない場合、コンパイラーが、既知の値を必要に応じてインライン化できない理由を理解してください。コンパイラは非常にスマートです。

コンパイルされたプログラムは、ソースコード内の行の1対1のマッピングであると考えられますが、これはほとんど真実ではありません。 C++の全体的な目的は、プログラムの実行時にコンピューターが実際に実行することのabstractionであることです。

いいえ、示されたコードを考えると、コンパイラは、提案された最適化に目に見える違いがないことを保証できず、最新のコンパイラは2番目の関数呼び出しを最適化できません。

非常に簡単な例:このクラスメソッドは、乱数ジェネレーターを使用し、結果をプライベートバッファーに保存し、コードの他の部分が後で読み取ります。明らかに、関数呼び出しを削除すると、そのバッファーに配置されるランダムに生成される値が少なくなります。

言い換えると、クラスメソッドがconstであるからといって、呼び出されたときに観察可能な副作用がないことを意味するわけではありません。

10
Sam Varshavchik

いいえ、この場合、コンパイラはこれを行うことができません。 constは、メソッドが属するオブジェクトの状態を変更しないことのみを意味します。ただし、同じ入力パラメーターでこのメソッドを複数回呼び出すと、異なる結果が得られる場合があります。たとえば、ランダムな結果を生成するメソッドを考えてください。

4
Axel

はい、最新のCコンパイラは、冗長関数呼び出しifとifを排除できます。このような最適化がas-if元のプログラムのセマンティクスに従いました。たとえば、関数に副作用がなく、戻り値が引数のみに依存している場合、同じ引数を使用して同じ関数を複数回呼び出す必要がないことを意味します。

さて、あなたはconstについて具体的に尋ねました-これは、コーダーではなく、開発者にとって最も有用です。 const関数はhintsであり、メソッドは呼び出されたオブジェクトを変更せず、const引数はhints引数が変更されないこと。ただし、関数は(合法的に1constポインタまたはその引数のthis- nessをキャストします。そのため、コンパイラはそれに依存できません。

さらに、関数に渡されたconstオブジェクトがその関数内で実際に変更されておらず、const関数がレシーバーオブジェクトを変更していない場合でも、メソッドは変更可能なグローバルデータに簡単に依存できますそのようなデータ)。たとえば、現在の時刻を返す関数、またはグローバルカウンターをインクリメントする関数を考えます。

したがって、const宣言は、コンパイラではなくプログラマに役立ちます2

ただし、コンパイラーは他のトリックを使用して、呼び出しが冗長であることを証明できる場合があります。

  • 関数は呼び出し元と同じコンパイル単位にある場合があり、コンパイラーはそれを検査して、何に依存しているかを正確に判断できます。これの究極の形式はインライン化です:関数本体は呼び出し元に移動でき、その時点でオプティマイザーは後の呼び出しから冗長コードを削除できます(最大でそれらの呼び出しからすべてのコードを、元の呼び出しのすべてまたはポートを含むまで)も))。
  • ツールチェーンは、あるタイプのリンク時最適化を使用する場合があります。これにより、異なるコンパイル単位の関数や呼び出し元に対しても、上記のポイントで説明したタイプの分析を効果的に実行できます。これにより、最終的な実行可能ファイルの生成時に存在するanyコードのこの最適化が可能になります。
  • コンパイラーは、ユーザーが関数に副作用がないと見なす可能性があることをコンパイラーに通知する属性で関数に注釈を付けることができます。たとえば、gccは pure およびconst関数属性を提供します。これらの属性は、関数に副作用がなく、パラメーターのみに依存することをgccに通知します(およびpureの場合はグローバル変数について)。

通常、オブジェクトが元々constとして定義されていなかった限り。

ある意味で、constdefinitionsがコンパイラを支援する:constとして定義されたグローバルオブジェクトを読み取り専用セクションに入れることができる実行可能ファイル(そのような機能が存在する場合)およびそれらのオブジェクトが等しい場合(たとえば、同じ文字列定数)を組み合わせます。

1
BeeOnRope