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 s
objek (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 o1
kapan o2
, yang tumpang tindih, ditulis. Apakah masih diizinkan mengakses bagian yang belum dibuka ( o1->a
)? Apakah mengakses bagian clobbered o1->b
sama 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 int
dan short
bukan 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.
Jawaban:
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->q
arti(*p).q
dan 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
tidaks
(karena sebagian telah ditimpa).Alasan untuk interpretasi ini dapat dilihat dalam fungsi seperti:
Dengan interpretasi ini, baris terakhir dapat dioptimalkan
puts("5");
, tetapi tanpa itu, kompiler harus mempertimbangkan bahwa pemanggilan fungsi mungkinf(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
int
anggota pada offset yang berbeda.sumber
f(s* s1, s* s2)
, tanparestrict
, kompiler tidak dapat mengasumsikans1
dans2
merupakan pointer yang berbeda. Saya pikir , sekali lagi tanparestrict
, bahkan tidak bisa berasumsi mereka tidak tumpang tindih sebagian. IAC, saya tidak melihat bahwa kepedulian OP baik ditunjukkan olehf()
analogi. Semoga berhasil, tidak mengganggu. UV untuk paruh pertama.s1 == s2
akan diizinkan, tetapi tidak tumpang tindih sebagian. (Optimasi dalam contoh kode saya masih dapat dilakukan jikas1 == s2
)int
struct (dan sistem dengan_Alignof(int) < sizeof(int)
).p->q
dan(*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.E1.E2
ekspresi tidak melakukan akses (maksud saya seluruhE1
ekspresi. Beberapa subekspresi dapat melakukan akses. YaituE1
adalah(*p)
, kemudian membaca nilai penunjuk ketika mengevaluasip
adalah akses, tetapi evaluasi*p
atau(*p)
tidak melakukan mengakses). Aturan aliasing yang ketat tidak berlaku jika tidak ada akses.