Apa semantik objek yang tumpang tindih di C?

25

Pertimbangkan struct berikut:

struct s {
  int a, b;
};

Biasanya 1 , struct ini akan memiliki ukuran 8 dan alignment 4.

Bagaimana jika kita membuat dua struct sobjek (lebih tepatnya, kita menulis ke dalam penyimpanan yang dialokasikan dua objek tersebut), dengan objek kedua tumpang tindih yang pertama?

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);

Apakah ada sesuatu tentang program ini perilaku yang tidak terdefinisi? Jika demikian, di mana itu menjadi tidak terdefinisi? Jika bukan UB, apakah dijamin untuk selalu mencetak yang berikut ini:

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

Secara khusus, saya ingin tahu apa yang terjadi pada objek yang ditunjukkan oleh o1kapan o2, yang tumpang tindih, ditulis. Apakah masih diizinkan mengakses bagian yang belum dibuka ( o1->a)? Apakah mengakses bagian clobbered o1->bsama dengan mengakses o2->a?

Bagaimana tipe efektif berlaku di sini? Aturannya cukup jelas ketika Anda berbicara tentang objek yang tidak tumpang tindih dan pointer yang menunjuk ke lokasi yang sama dengan toko terakhir, tetapi ketika Anda mulai berbicara tentang jenis bagian efektif dari objek atau objek yang tumpang tindih, itu kurang jelas.

Adakah yang akan berubah jika tulisan kedua dari jenis yang berbeda? Jika anggota mengatakan intdan shortbukan dua int?

Ini adalah godbolt jika Anda ingin bermain dengannya di sana.


1 Jawaban ini berlaku untuk platform di mana ini tidak terjadi juga: misalnya, beberapa mungkin memiliki ukuran 4 dan penyelarasan 2. Pada platform di mana ukuran dan penyelarasan adalah sama, pertanyaan ini tidak akan berlaku karena sejajar, objek yang tumpang tindih akan tidak mungkin, tapi saya tidak yakin apakah ada platform seperti itu.

BeeOnRope
sumber
2
Saya cukup yakin ini adalah UB, tapi saya akan membiarkan pengacara bahasa memberikan bab dan ayat.
Barmar
Saya pikir bahwa kompiler C pada sistem vektor Cray lama memaksa perataan dan ukuran menjadi sama, dengan model ILP64 dan perataan paksa 64-bit (alamat adalah kata-kata 64-bit - tanpa alamat byte). Tentu saja ini menimbulkan banyak masalah lain ....
John D McCalpin

Jawaban:

15

Pada dasarnya ini semua area abu-abu dalam standar; aturan aliasing yang ketat menentukan kasus dasar dan meninggalkan pembaca (dan vendor kompiler) untuk mengisi rinciannya.

Sudah ada upaya untuk menulis aturan yang lebih baik tetapi sejauh ini mereka belum menghasilkan teks normatif dan saya tidak yakin apa status ini untuk C2x.

Seperti yang disebutkan dalam jawaban saya untuk pertanyaan Anda sebelumnya, interpretasi yang paling umum adalah p->qarti (*p).qdan tipe efektif berlaku untuk semua *p, meskipun kita kemudian melanjutkan untuk menerapkan .q.

Di bawah interpretasi ini, printf("o1.a=%d\n", o1->a);akan menyebabkan perilaku yang tidak terdefinisi karena tipe lokasi yang efektif*o1 tidak s(karena sebagian telah ditimpa).

Alasan untuk interpretasi ini dapat dilihat dalam fungsi seperti:

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

Dengan interpretasi ini, baris terakhir dapat dioptimalkan puts("5");, tetapi tanpa itu, kompiler harus mempertimbangkan bahwa pemanggilan fungsi mungkin f(o1, o2);dan karenanya kehilangan semua manfaat yang konon disediakan oleh aturan aliasing yang ketat.

Argumen serupa berlaku untuk dua tipe struct yang tidak terkait yang keduanya memiliki intanggota pada offset yang berbeda.

MM
sumber
1
Dengan f(s* s1, s* s2), tanpa restrict, kompiler tidak dapat mengasumsikan s1dan s2merupakan pointer yang berbeda. Saya pikir , sekali lagi tanpa restrict, bahkan tidak bisa berasumsi mereka tidak tumpang tindih sebagian. IAC, saya tidak melihat bahwa kepedulian OP baik ditunjukkan oleh f()analogi. Semoga berhasil, tidak mengganggu. UV untuk paruh pertama.
chux - Reinstate Monica
@ chux-ReinstateMonica tanpa batasan, s1 == s2akan diizinkan, tetapi tidak tumpang tindih sebagian. (Optimasi dalam contoh kode saya masih dapat dilakukan jika s1 == s2)
MM
@ chux-ReinstateMonica Anda juga bisa mempertimbangkan masalah yang sama hanya dengan intstruct (dan sistem dengan _Alignof(int) < sizeof(int)).
MM
3
Status pertanyaan semacam ini mengenai tipe efektif untuk C2x cukup terbuka dan masih menjadi bahan perdebatan dalam kelompok studi. Berhati-hatilah dengan mengklaim kesetaraan p->qdan (*p).q. Ini mungkin benar untuk jenis interpreasi seperti yang Anda nyatakan, tetapi tidak benar dari sudut pandang operasional. Penting untuk akses bersamaan ke struktur yang sama bahwa akses anggota tidak menyiratkan akses anggota lain.
Jens Gustedt
Aturan aliasing yang ketat adalah tentang akses . Ekspresi sisi kiri dalam E1.E2ekspresi tidak melakukan akses (maksud saya seluruh E1ekspresi. Beberapa subekspresi dapat melakukan akses. Yaitu E1adalah (*p), kemudian membaca nilai penunjuk ketika mengevaluasi padalah akses, tetapi evaluasi *patau (*p)tidak melakukan mengakses). Aturan aliasing yang ketat tidak berlaku jika tidak ada akses.
Pengacara Bahasa