欠陥レポート 1400のように見えます: 関数ポインターの等価性がこの問題に対処しており、この最適化を行っても問題ないと私には思われますが、コメントが示すように、意見の相違があります。それは言います(私の強調):
5.10 [expr.eq] パラグラフ 2 によると、2 つの関数ポインタは、同じ関数を指している場合にのみ等しいと比較されます。ただし、最適化として、実装は現在、同一の定義を持つ関数をエイリアシングしています。標準がこの最適化を明示的に処理する必要があるかどうかは明らかではありません。
応答は次のとおりです。
標準は要件を明確にしており、実装は「as-if」ルールの制約内で自由に最適化できます。
質問は2つの問題について尋ねています:
- これらのポインターが等しいと見なされても問題ありませんか
- 機能を合体させてもいいですか
コメントに基づいて、応答の 2 つの解釈が表示されます。
この最適化は問題ありません。標準は、as-if ルールの下で実装にこの自由を与えます。as-if ルールはセクションで説明1.9
されており、実装は標準の要件に関して観察可能な動作をエミュレートするだけでよいことを意味します。これはまだ応答の私の解釈です。
目前の問題は完全に無視されており、ステートメントは、仮定のルールがこれをカバーしていることは明らかであるため、標準への調整は不要であると述べているだけですが、解釈は読者の演習として残されています。回答が簡潔であるため、この見解を却下できないことは認めますが、最終的にはまったく役に立たない回答になります。またNAD
、私が知る限り、問題が存在するかどうかを指摘している他の問題の回答とは矛盾しているようです。
ドラフト規格の内容
as-if ルールを扱っていることがわかっているので、そこから始めて、次のセクションに注意してください1.8
。
オブジェクトがビットフィールドまたはサイズがゼロの基本クラスのサブオブジェクトでない限り、そのオブジェクトのアドレスは、それが占有する最初のバイトのアドレスです。ビット フィールドではない 2 つのオブジェクトは、一方が他方のサブオブジェクトである場合、または少なくとも 1 つがサイズ 0 の基本クラス サブオブジェクトであり、それらが異なる型である場合、同じアドレスを持つことができます。それ以外の場合、それらは別個のアドレスを持つものとします。4
そしてメモ4
は言う:
「as-if」ルールの下では、プログラムが違いを観察できない場合、実装は同じマシン アドレスに 2 つのオブジェクトを格納するか、オブジェクトをまったく格納しないことが許可されます。
しかし、そのセクションのメモには次のように書かれています。
オブジェクトのようにストレージを占有するかどうかに関係なく、関数はオブジェクトではありません
規範的ではありませんが、パラグラフで説明されているオブジェクトの要件は1
、関数のコンテキストでは意味をなさないため、この注記と一致しています。そのため、いくつかの例外を除いて、オブジェクトのエイリアシングは明示的に制限されていますが、そのような制限は関数には適用されません。
次に、5.10
等値演算子のセクションがあります (強調鉱山):
[...] 2 つのポインターは、両方が null である場合、両方が同じ関数を指している場合、または両方が同じアドレス(3.9.2) を表している場合は等しいと見なされます。それ以外の場合は等しくありません。
これは、次の場合に 2 つのポインターが等しいことを示しています。
または両方が同じアドレスを表すことで、コンパイラが 2 つの異なる関数にエイリアスを設定できるように十分な許容範囲が与えられ、異なる関数を比較するために異なる関数へのポインターを必要としないようです。
観察
Keith Thompson は、重要な問題に関係しているため、回答に追加する価値があると思われるいくつかの素晴らしい観察を行っています。
プログラムが&foo == &barの結果を出力する場合、それは観察可能な動作です。問題の最適化は、観察可能な動作を変更します。
私は同意します。ポインターが等しくないという要件があることを示すことができれば、実際にas-if ルールに違反しますが、これまでのところそれを示すことはできません。
と:
[...]空の関数を定義し、それらのアドレスを一意の値として使用するプログラムを考えてみましょう (
< signal.h> / <csignal>のSIG_DFL、SIG_ERR、およびSIG_IGNについて考えてください)。それらに同じアドレスを割り当てると、そのようなプログラムが壊れます
私のコメントで指摘したように、C 標準では、これらのマクロがC11とは異なる値を生成する必要があります。7.14
[...]これは、シグナル関数の 2 番目の引数およびその戻り値と互換性のある型を持ち、その値が宣言可能な関数のアドレスと等しくない個別の値を持つ定数式に展開されます[...]
したがって、このケースはカバーされていますが、この最適化を危険にする他のケースがあるかもしれません。
アップデート
開発者である Jan Hubička は、GCC 5 でのリンク時間と手続き間の最適化の改善に関するgcc
ブログ記事を書きました。コードの折り畳みは、彼がカバーした多くのトピックの 1 つです。
私は彼に、同一の関数を同じアドレスに折りたたむことが適合動作であるかどうかについてコメントするように依頼しましたgcc
.
ターン 2 関数が同じアドレスを持つように準拠していないため、MSVC はここで非常に積極的です。たとえば、これを行うと、GCC 自体が壊れます。これは、驚いたことに、プリコンパイル済みヘッダー コードでアドレス比較が行われるためです。Firefox を含む他の多くのプロジェクトで機能します。
振り返ってみると、何ヶ月も欠陥レポートを読み、最適化の問題について考えた結果、私は委員会の反応をより保守的に読む傾向にありました。関数のアドレスを取得することは観察可能な動作であるため、同一の関数を折りたたむことはas-if ルールに違反します。
更新 2
このllvm-dev の議論も参照してください: 長さゼロの関数ポインタの等価性:
これは、link.exe のよく知られた適合性違反のバグです。LLVM 自体が同様のバグを導入して事態を悪化させるべきではありません。よりスマートなリンカー (たとえば、lld と gold の両方だと思います) は、関数シンボルの 1 つを除くすべてが呼び出しのターゲットとしてのみ使用される場合にのみ (そして実際にアドレスを観察しない場合)、同一の関数を組み合わせて実行します。そして、はい、この不適合な動作は (めったに) 実際には物事を壊します。この
研究論文を参照してください。