これは私のpureとconstの理解に基づいて正しいと言えますが、誰かがこの 2 つの正確な定義を持っている場合は、率直に話してください。GCCのドキュメントでは、関数が「戻り値以外に影響を与えない」( pureの場合) または「引数以外の値を調べない」( constの場合) とはどういう意味かを正確に説明していないため、これは注意が必要です。明らかに、すべての関数にはいくつかの効果があり (プロセッサ サイクルの使用、メモリの変更)、いくつかの値 (関数コード、定数) を調べます。
「副作用」は、C プログラミング言語のセマンティクスの観点から定義する必要がありますが、これらの属性の目的に基づいて、GCC の人々が何を意味するかを推測できます。これは、追加の最適化を有効にすることです (少なくとも、それは私が用であると仮定します)。
以下のいくつかが基本的すぎる場合はご容赦ください...
純粋な関数は、共通部分式の削除に参加できます。それらの機能は、環境を変更しないことです。したがって、コンパイラは、プログラムのセマンティクスを変更することなく、より少ない回数で自由に呼び出すことができます。
z = f(x);
y = f(x);
になります:
z = y = f(x);
z
または、とが使用されていない場合は完全に削除されy
ます。
したがって、「純粋」の実用的な定義は、「プログラムのセマンティクスを変更することなく、より少ない回数で呼び出すことができる関数」であると私は推測しています。ただし、関数呼び出しは移動できません。たとえば、
size_t l = strlen(str); // strlen is pure
*some_ptr = '\0';
// Obviously, strlen can't be moved here...
const 関数は動的環境に依存しないため、順序を変更できます。
// Assuming x and y not aliased, sin can be moved anywhere
*some_ptr = '\0';
double y = sin(x);
*other_ptr = '\0';
したがって、「const」の実用的な定義は、「プログラムのセマンティクスを変更せずにいつでも呼び出すことができる関数」であると私は推測しています。ただし、危険があります。
__attribute__((const))
double big_math_func(double x, double theta, double iota)
{
static double table[512];
static bool initted = false;
if (!initted) {
...
initted = true;
}
...
return result;
}
これは const であるため、コンパイラはそれを並べ替えることができます...
pthread_mutex_lock(&mutex);
...
z = big_math_func(x, theta, iota);
...
pthread_mutex_unlock(&mutex);
// big_math_func might go here, if the compiler wants to
この場合、コードのクリティカル セクション内にのみ表示されますが、2 つのプロセッサから同時に呼び出される可能性があります。table
その後、プロセッサは、 への変更が既に完了した後に への変更を延期することを決定できますがinitted
、これは悪いニュースです。これは、メモリ バリアまたはpthread_once
.
このバグが x86 で現れることはないと思います。また、複数の物理プロセッサ (コアではない) を持たない多くのシステムでも現れるとは思いません。そのため、デュアル ソケットの POWER コンピュータでは、何年も問題なく動作していたのに、突然機能しなくなります。
結論:これらの定義の利点は、コンパイラがこれらの属性の存在下でどのような変更を行うことが許可されているかを明確にすることです。これは (私が思うに) GCC ドキュメントではややあいまいです。欠点は、これらが GCC チームによって使用されている定義であるかどうかが明確でないことです。
たとえば、Haskell 言語の仕様を見ると、Haskell 言語にとって純度は非常に重要であるため、純度のより正確な定義を見つけることができます。
編集: GCC または Clang を強制して、単一の__attribute__((const))
関数呼び出しを別の関数呼び出しに移動させることはできませんでしたが、将来、そのようなことが起こる可能性は十分にあるようです。がデフォルトになったときのことを覚え-fstrict-aliasing
ていますか? 誰もが突然、プログラムに多くのバグを抱えていましたか? そういうことで、私は慎重になります。
function をマークする__attribute__((const))
と、パラメーターが同じである限り、プログラムの実行中にいつ呼び出されても、関数呼び出しの結果が同じであることをコンパイラーに約束しているようです。
ただし、const 関数をクリティカル セクションから移動する方法を思いつきましたが、その方法は一種の「不正行為」と呼ばれる可能性があります。
__attribute__((const))
extern int const_func(int x);
int func(int x)
{
int y1, y2;
y1 = const_func(x);
pthread_mutex_lock(&mutex);
y2 = const_func(x);
pthread_mutex_unlock(&mutex);
return y1 + y2;
}
コンパイラはこれを (アセンブリから) 次のコードに変換します。
int func(int x)
{
int y;
y = const_func(x);
pthread_mutex_lock(&mutex);
pthread_mutex_unlock(&mutex);
return y * 2;
}
これは、属性のみでは発生せず__attribute__((pure))
、属性のみがこの動作をトリガーすることに注意してください。const
const
ご覧のとおり、クリティカル セクション内の呼び出しが消えました。以前の呼び出しが保持されたのはかなり恣意的なようであり、コンパイラーが将来のバージョンで、どの呼び出しを保持するか、または関数呼び出しをどこかに移動するかどうかについて別の決定を下さないことに賭けるつもりはありません。それ以外は完全に。
結論 2:慎重に検討してください。コンパイラーに対してどのような約束をしているのかを知らなければ、コンパイラーの将来のバージョンに驚くかもしれません。