Cでのオブジェクトのオーバーラップのセマンティクスは何ですか?


25

次の構造体について考えてみましょう。

struct s {
  int a, b;
};

典型的には1、この構造体は、サイズ8と位置合わせ4を有するであろう。

2つのstruct sオブジェクトを作成し(より正確には、2つのオブジェクトを割り当てられたストレージに書き込みます)、2番目のオブジェクトが最初のオブジェクトと重なる場合はどうなりますか?

char *storage = malloc(3 * sizeof(struct s));
struct s *o1 = (struct s *)storage; // offset 0
struct s *o2 = (struct s *)(storage + alignof(struct s)); // offset 4

// now, o2 points half way into o1
*o1 = (struct s){1, 2};
*o2 = (struct s){3, 4};

printf("o2.a=%d\n", o2->a);
printf("o2.b=%d\n", o2->b);
printf("o1.a=%d\n", o1->a);
printf("o1.b=%d\n", o1->b);

このプログラムについて未定義の動作はありますか?もしそうなら、それはどこで未定義になりますか?UBでない場合、常に以下を出力することが保証されていますか?

o2.a=3
o2.b=4
o1.a=1
o1.b=3

特に、それがオーバーラップしているが書き込まれたo1ときo2にポイントされたオブジェクトがどうなるか知りたい。取り除かれていない部分(o1->a)へのアクセスは引き続き許可されますか?破壊された部分へのアクセスは、アクセスo1->bと同じo2->aですか?

ここで効果的なタイプはどのように適用されますか?オーバーラップしていないオブジェクトと、最後のストアと同じ場所を指すポインターについて話しているときは、ルールは十分に明確ですが、オブジェクトの部分の有効なタイプまたはオーバーラップしているオブジェクトについて話し始めると、それほど明確ではありません。

2回目の書き込みが別のタイプの場合、何か変更はありますか?メンバーが言ったintとしたら、short2 intつではなく?

ここでプレイしたい場合は、ここにゴッドボルトがあります。


1この回答は、これが当てはまらないプラットフォームにも当てはまります。たとえば、サイズ4と配置2があるプラットフォームもあります。サイズと配置が同じであるプラットフォームでは、配置されたオブジェクトが重なるため、この質問は当てはまりません。不可能ではありますが、そのようなプラットフォームがあるかどうかはわかりません。


2
私はそれがUBであると確信していますが、言語弁護士に章と節を提供させてください。
Barmar

古いCrayベクターシステムのCコンパイラは、ILP64モデルと強制64ビットアライメントを使用して、アライメントとサイズを同じに強制したと思います(アドレスは64ビットワードです-バイトアドレッシングはありません)。もちろん、これにより他の多くの問題が発生しました...
John D McCalpin

回答:


15

基本的に、これは規格のすべての灰色の領域です。厳密なエイリアシングルールは基本的なケースを指定し、リーダー(およびコンパイラベンダー)が詳細を入力するようにします。

より良いルールを書く努力がなされてきましたが、これまでのところそれらは規範的なテキストをもたらしておらず、これのステータスがC2xにとって何であるかわかりません。

以前の質問に対する私の回答で述べたように、最も一般的な解釈はそのp->q手段で(*p).qあり、有効なタイプはすべてに適用されますが*p、その後適用されます.q

この解釈でprintf("o1.a=%d\n", o1->a);は、場所の有効なタイプ*o1はないためs(一部が上書きされているため)、未定義の動作が発生します。

この解釈の根拠は、次のような関数で確認できます。

void f(s* s1, s* s2)
{
    s2->a = 5;
    s1->b = 6;
    printf("%d\n", s2->a);
}

この解釈により、最後の行をに最適化できますがputs("5");、それがないと、コンパイラーは、関数呼び出しがそうであった可能性がf(o1, o2);あり、したがって厳密なエイリアシングルールによって提供されるとされるすべての利点を失う可能性があることを考慮する必要があります。

同様の議論が、2つの無関係な構造体タイプにも当てはまり、どちらもint異なるオフセットにメンバーが存在します。


1
を使用するとf(s* s1, s* s2)、を使用しないrestrictと、コンパイラは別のポインタであると想定できなくs1なりs2ます。私は考えてなくて、再び、restrictそれも彼らは部分的に重複しないと仮定することはできません。IAC、OPの懸念がf()アナロジーによって十分にデモされているとは思いません。がんばって頑張ってください。前半はUV。
chux-モニカを

制限なしの@ chux-ReinstateMonica s1 == s2は許可されますが、部分的に重複することはできません。(私のコード例の最適化は、次の場合でも実行できますs1 == s2
MM

@ chux-ReinstateMonica intでは、構造体(およびを使用したシステム_Alignof(int) < sizeof(int))の代わりに同じ問題を検討することもできます。
MM

3
C2xの効果的なタイプに関するこの種の質問のステータスは、かなりオープンで、まだ研究グループで議論されています。等価性を主張して慎重にかかわらうp->q(*p).q。これは、あなたが述べるタイプの解釈については当てはまるかもしれませんが、運用の観点からは当てはまりません。同じ構造への同時アクセスでは、メンバーのアクセスが他のメンバーのアクセスを意味しないことが重要です。
イェンスガステッド

厳密なエイリアシングルールはアクセスに関するものです。左側の表現E1.E2式はアクセスを行いません(私は、全体の意味E1表現を。その部分式のいくつかは、アクセスを行うことができる。すなわち場合E1(*p)、その後、評価する際、ポインタの値を読み取ることはpアクセスが、の評価である*pか、(*p)いずれかを実行しません。アクセス)。アクセスがない場合、厳格なエイリアシングルールは適用されません。
言語弁護士
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.