web-dev-qa-db-ja.com

const-correctnessは、コンパイラーに最適化の余地を与えますか?

読みやすさが向上し、プログラムのエラーが発生しにくくなることは知っていますが、パフォーマンスはどの程度向上しますか?

ちなみに、参照とconstポインタの主な違いは何ですか?それらは異なる方法でメモリに格納されていると思いますが、どのようにそうですか?

76
slartibartfast

[編集:わかりました。この質問は最初に思ったよりも微妙です。]

Constへのポインターまたはconstの参照を宣言しても、コンパイラーが何かを最適化するのに役立つことはありません。 (ただし、この回答の下部にある更新を参照してください。)

const宣言は、その宣言のscope内で識別子がどのように使用されるかを示すだけです。基になるオブジェクトが変更できないということではありません。

例:

_int foo(const int *p) {
    int x = *p;
    bar(x);
    x = *p;
    return x;
}
_

コンパイラは、_*p_がbar()の呼び出しによって変更されないと想定することはできません。これは、pが(たとえば)グローバルintへのポインタであり、bar()が可能性があるためです。それを変更します。

コンパイラがfoo()の呼び出し元とbar()の内容について十分に知っていて、bar()が_*p_を変更しないことを証明できる場合、const宣言なしでその証明を実行することもできます

しかし、これは一般的に当てはまります。 constは宣言のスコープ内でのみ効果があるため、コンパイラーは、そのスコープ内でポインターまたは参照をどのように処理しているかを既に確認できます。基になるオブジェクトを変更していないことはすでにわかっています。

つまり、このコンテキストでconstが行うことは、間違いを防ぐことだけです。コンパイラにまだ知らないことは何も伝えないので、最適化には関係ありません。

foo()を呼び出す関数はどうですか?お気に入り:

_int x = 37;
foo(&x);
printf("%d\n", x);
_

foo()は_const int *_を取るので、コンパイラはこれが37を出力することを証明できますか?

いいえ。foo()はconstへのポインタを取りますが、const-nessをキャストして、intを変更する可能性があります。 (これはnot未定義の動作です。)ここでも、コンパイラーは一般的に仮定を行うことはできません。そして、そのような最適化を行うのに十分なfoo()について知っていれば、constがなくてもそれを知ることができます。

constが最適化を許可するのは、次のような場合のみです。

_const int x = 37;
foo(&x);
printf("%d\n", x);
_

ここで、任意のメカニズムを介してxを変更する(たとえば、ポインターを取得してconstをキャストする)ことは、未定義の振る舞いを呼び出すことです。したがって、コンパイラはそれを行わないと自由に想定でき、定数37をprintf()に伝播できます。この種の最適化は、constを宣言するすべてのオブジェクトに対して有効です。 (実際には、参照を取得しないローカル変数は、コンパイラーがそのスコープ内で変更したかどうかを既に確認できるため、メリットがありません。)

「サイドノート」の質問に答えるために、(a)constポインターはポインターです。 (b)constポインターはNULLに等しくなる可能性があります。あなたは、内部表現(つまり住所)がおそらく同じであるということは正しいです。

[更新]

Christoph がコメントで指摘しているように、restrictについて言及していないため、私の答えは不完全です。

C99標準のセクション6.7.3.1(4)には、次のように書かれています。

Bの各実行中に、LをPに基づく&Lを持つ任意の左辺値とします。Lが指定されたオブジェクトXの値にアクセスするために使用され、Xも(何らかの方法で)変更される場合、次の要件が適用されます。 :Tはconst-qualifiedであってはなりません。 .。

(ここで、Bは、Tへの制限ポインターであるPがスコープ内にある基本ブロックです。)

したがって、C関数foo()が次のように宣言されている場合:

_foo(const int * restrict p)
_

...次に、コンパイラmayは、pの存続期間中に_*p_への変更が発生しないと想定します。 foo()の実行中-そうでない場合、動作は未定義になります。

したがって、原則として、restrictをconstへのポインターと組み合わせると、上記で却下された両方の最適化が可能になります。実際にそのような最適化を実装しているコンパイラはありますか? (少なくとも、GCC 4.5.2はそうではありません。)

restrictは、コンパイラ固有の拡張機能を除いて、C++ではなく(C++ 0xでさえも)Cにのみ存在することに注意してください。

69
Nemo

頭から離れて、適切なconst-qualificationによって追加の最適化が可能になる2つのケースを考えることができます(プログラム全体の分析が利用できない場合)。

const int foo = 42;
bar(&foo);
printf("%i", foo);

ここで、コンパイラは、fooへのすべての変更が違法であるため、bar()の本文(現在の変換ユニットには表示されない可能性があります)を調べることなく42を出力することを認識しています( これは Nemoの例)と同じです。

ただし、これは、bar()を次のように宣言することにより、fooconstとしてマークせずに行うこともできます。

extern void bar(const int *restrict p);

多くの場合、プログラマーは実際にはrestrict-修飾されたconstへのポインターを望んでおり、関数パラメーターとしてはconstへの単純なポインターではありません。ポインティングされたオブジェクトの可変性について。

あなたの質問の2番目の部分に関して:すべての実用的な目的のために、C++参照は自動間接参照を伴う定数ポインター(定数値へのポインターではありません!)と考えることができます-それは「より安全」または「より速い」ではありませんポインタよりも便利です。

6
Christoph

C++のconstには2つの問題があります(最適化に関する限り)。

  • const_cast
  • mutable

const_castは、const参照またはconstポインターでオブジェクトを渡しても、関数がconst-nessをキャストしてオブジェクトを変更する可能性があることを意味します(オブジェクトが最初からconstでない場合に許可されます)。

mutableは、オブジェクトがconstであっても、その一部が変更される可能性があることを意味します(キャッシュ動作)。また、(所有されるのではなく)ポイントされたオブジェクトは、論理的にオブジェクト状態の一部である場合でも、constメソッドで変更できます。そして最後に、グローバル変数も変更できます...

constは、開発者が論理的な間違いを早期に発見できるようにするためのものです。

5
Matthieu M.

const-correctnessは通常、パフォーマンスに役立ちません。ほとんどのコンパイラは、フロントエンドを超えて定数を追跡することすらしません。状況によっては、変数をconstとしてマークすると役立つ場合があります。

参照とポインタはまったく同じ方法でメモリに保存されます。

4
servn

これは実際にはコンパイラ/プラットフォームに依存します(まだ作成されていないコンパイラ、または使用したことがないプラットフォームでの最適化に役立つ場合があります)。 CおよびC++標準は、一部の関数に複雑さの要件を与えること以外、パフォーマンスについては何も述べていません。

Const修飾は状況によっては合法的に破棄される可能性があり、オブジェクトが別の非const参照によって変更される可能性があるため、constへのポインターと参照は通常最適化に役立ちません。一方、オブジェクトをconstとして宣言すると、オブジェクトを変更できないことが保証されるため(コンパイラーが定義を知らない関数に渡された場合でも)、役立つ場合があります。これにより、コンパイラーはconstオブジェクトを読み取り専用メモリーに保管したり、その値を一元化された場所にキャッシュしたりできるため、コピーや変更のチェックの必要性が減ります。

ポインタと参照は通常、まったく同じ方法で実装されますが、これもプラットフォームに完全に依存します。本当に興味がある場合は、yourプラットフォームとyourプログラムのコンパイラ用に生成されたマシンコードを確認する必要があります(実際にマシンコードを生成するコンパイラを使用している場合)。

2
Mankarse

1つは、グローバル変数constを宣言すると、ライブラリまたは実行可能ファイルの読み取り専用部分に配置して、読み取り専用mmapを使用して複数のプロセス間で共有できる場合があることです。少なくともグローバル変数で宣言されたデータがたくさんある場合、これはLinuxで大きなメモリ獲得になる可能性があります。

別の状況では、定数のグローバル整数、またはfloatまたはenumを宣言すると、コンパイラーは変数参照を使用するのではなく、定数をインラインに配置できる場合があります。私はコンパイラの専門家ではありませんが、それは少し速いと思います。

参照は、実装に関しては、その下にある単なるポインタです。

1
Havoc P

パフォーマンスを少し向上させることができますが、宣言を介してオブジェクトに直接アクセスしている場合に限ります。参照パラメーターなどは最適化できません。元々constとして宣言されていないオブジェクトへの他のパスが存在する可能性があり、コンパイラーは通常、参照しているオブジェクトが実際にconstとして宣言されているかどうかを、それが使用している宣言でない限り判断できません。

Const宣言を使用している場合、コンパイラーは、外部でコンパイルされた関数本体などがそれを変更できないことを認識しているため、そこでメリットが得られます。そしてもちろん、const intのようなものはコンパイル時に伝播されるので、それは(単なるintと比較して)大きな勝利です。

参照とポインタはまったく同じように格納され、構文的に異なる動作をするだけです。参照は基本的に名前の変更であるため、比較的安全ですが、ポインターはさまざまなものを指すことができるため、より強力でエラーが発生しやすくなります。

Constポインターはアーキテクチャ上は参照と同じであるため、マシンコードと効率は同じになると思います。本当の違いは構文です。参照はよりクリーンで読みやすい構文であり、ポインターによって提供される追加の機構は必要ないため、参照はスタイル的に優先されます。

0
d00t