標準のすべてのバージョンは、多くのエイリアシング構造のサポートを実装品質の問題として扱ってきました。これは、すべての有用な構造をサポートし、有用な最適化をブロックせず、すべてのコンパイラでサポートできるルールを作成することは本質的に不可能だったためです。大幅な手直しなし。次の関数について考えてみます。
struct foo {int length; int *dat; };
int test1(struct foo *p)
{
int *ip = &p->length;
*ip = 2;
return p->length;
}
struct foo
型のオブジェクトがへの割り当てによって影響を受ける可能性を処理するために、高品質のコンパイラが期待されるべきであることはかなり明らかだと思います*ip
。一方、次の関数について考えてみます。
void test2(struct foo *p)
{
int i;
for (i=0; i < p->length; i++)
p->dat[i] = 0;
}
コンパイラは、書き込みがp->dat[i]
の値に影響を与える可能性を考慮に入れる必要がありますか?たとえば、少なくともループの最初の反復後にp->length
の値をリロードすることによってですか?p->length
委員会の一部のメンバーは、コンパイラーがそのような許可をすることを要求することを意図したと思いますが、すべてがそうだったとは思いません。タイプのオブジェクトにアクセスしますがstruct foo
、int
その中にはありません。省略は偶然だと思う人もいるかもしれませんが、コンパイラーは、あるコンテキストで特定のタイプとしてアクセスされるオブジェクトには、目に見える関連性を持つ左辺値によってアクセスする必要があるとルールを解釈するという期待に基づいていたと思います。そのコンテキスト内で、リストされたタイプの1つのオブジェクトを使用します。何が「目に見える関連」を構成するのかという問題は、標準の管轄外のQoIの問題として残されましたが、コンパイラの作成者は、実用的な場合に関連を認識するために合理的な努力をすることが期待されていました。
のような関数内ではtest1
、型の左辺値がp
を導出するためip
に使用され、形成とその最後の使用の間でp
アクセスするために他の方法で使用されることはありません。したがって、コンパイラーは、関連のない構造体のメンバーにアクセスするために型のポインターを使用するための包括的な許可を与える一般的な規則がなくても、後で読み取るためにストアを並べ替えることができないことを認識するのに問題はありません。ただし、内では、のアドレスをポインタの計算に使用できた可能性のある目に見える手段はありません。したがって、最も一般的な目的を目的としたコンパイラを最適化して、p->length
ip
*ip
p->length
int*
int
test2
p->length
p->dat
p->length
その値が変更されないことを期待してループの前。
clangとgccは、ポインターの派生元のオブジェクトのタイプを認識するための努力をするのではなく、代わりに、標準がそれらのタイプのポインターを使用してstruct(unionではない!)メンバーにアクセスする一般的な許可を与えるかのように動作することを選択します。これは許容されますが、標準では必須ではありません(準拠しているが、ガベージ品質の実装はtest1
任意の無意味な方法で処理できます)が、ポインターの導出に対する盲目は、プログラマーが使用できる構造の範囲を不必要に制限し、有用なものを放棄する必要がありますによって例示されるような最適化test2()
。
全体として、Cでのエイリアシングに関連するほとんどすべての質問に対する正解は、「実装の品質の問題です」です。clangとgccが何をするかについての観察は-fstrict-aliasing
、それらのコンパイラーのモードをなだめる必要があるが、標準が実際に言っていることとはほとんど関係がない人々にとって役立つかもしれません。