Pertimbangkan tiga struct
s berikut :
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
Pada platform tipikal di mana int
4 byte, ukuran, pelurusan dan total padding 1 adalah sebagai berikut:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Tidak ada tumpang tindih antara penyimpanan blub
dan blob
anggota, meskipun ukuran 1 blob
pada prinsipnya "pas" di padding blub
.
C ++ 20 memperkenalkan no_unique_address
atribut, yang memungkinkan anggota kosong yang berdekatan untuk berbagi alamat yang sama. Ini juga secara eksplisit memungkinkan skenario yang dijelaskan di atas menggunakan padding dari satu anggota untuk menyimpan yang lain. Dari cppreference (penekanan saya):
Menunjukkan bahwa anggota data ini tidak perlu memiliki alamat yang berbeda dari semua anggota data non-statis lainnya di kelasnya. Ini berarti bahwa jika anggota memiliki tipe kosong (misalnya Allocator stateless), kompiler dapat mengoptimalkannya agar tidak menempati ruang, sama seperti jika itu adalah basis kosong. Jika anggota tidak kosong, bantalan ekor di dalamnya dapat juga digunakan kembali untuk menyimpan anggota data lainnya.
Memang, jika kita menggunakan atribut ini pada blub b0
, ukuran bla
tetes 8
, sehingga blob
memang disimpan dalam blub
seperti yang terlihat pada godbolt .
Akhirnya, kita sampai pada pertanyaan saya:
Teks apa dalam standar (C ++ 11 hingga C ++ 20) yang mencegah tumpang tindih ini no_unique_address
, untuk objek yang tidak dapat disalin secara sepele?
Saya perlu mengecualikan objek trivially copyable (TC) dari atas, karena untuk objek TC, diperbolehkan std::memcpy
dari satu objek ke yang lain, termasuk sub-objek anggota, dan jika penyimpanan tumpang tindih ini akan pecah (karena semua atau sebagian dari penyimpanan untuk anggota yang berdekatan akan ditimpa) 2 .
1 Kami menghitung padding hanya sebagai perbedaan antara ukuran struktur dan ukuran semua anggota konstituennya, secara rekursif.
2 Inilah sebabnya mengapa saya memiliki salinan konstruktor yang didefinisikan: untuk membuat blub
dan blob
tidak dapat disalin secara sepele .
sumber
Jawaban:
Standar ini sangat sunyi ketika berbicara tentang model memori dan tidak terlalu eksplisit tentang beberapa istilah yang digunakannya. Tapi saya pikir saya menemukan argumentasi yang berhasil (yang mungkin agak lemah)
Pertama, mari kita cari tahu apa yang bahkan merupakan bagian dari suatu objek. [basic.types] / 4 :
Jadi representasi objek
b0
terdiri darisizeof(blub)
unsigned char
objek, jadi 8 byte. Bit padding adalah bagian dari objek.Tidak ada objek yang dapat menempati ruang yang lain jika tidak bersarang di dalamnya [basic.life] /1.5 :
Jadi masa hidup
b0
akan berakhir, ketika penyimpanan yang ditempati olehnya akan digunakan kembali oleh objek lain, yaitub1
. Saya belum memeriksanya, tetapi saya pikir mandat standar bahwa sub objek objek yang hidup juga harus hidup (dan saya tidak bisa membayangkan bagaimana ini harus bekerja secara berbeda).Jadi penyimpanan yang
b0
menempati mungkin tidak digunakan olehb1
. Saya tidak menemukan definisi "menempati" dalam standar, tetapi saya pikir interpretasi yang masuk akal akan menjadi "bagian dari representasi objek". Dalam representasi objek deskripsi kutipan, kata-kata "mengambil" digunakan 1 . Di sini, ini akan menjadi 8 byte, jadibla
perlu setidaknya satu lagi untukb1
.Khusus untuk subobyek (jadi antara lain anggota data non-statis) ada juga ketentuan [intro.object] / 9 (tetapi ini ditambahkan dengan C ++ 20, thx @BeeOnRope)
(penekanan saya) Di sini lagi, kita memiliki masalah yang "menempati" tidak didefinisikan dan sekali lagi saya akan berdebat untuk mengambil byte dalam representasi objek. Perhatikan bahwa ada catatan kaki untuk ini [basic.memobj] / catatan kaki 29
Yang dapat memungkinkan kompiler untuk memecahkan ini jika dapat membuktikan bahwa tidak ada efek samping yang dapat diamati. Saya akan berpikir bahwa ini cukup rumit untuk hal mendasar seperti tata letak objek. Mungkin itu sebabnya optimasi ini hanya diambil ketika pengguna memberikan info bahwa tidak ada alasan untuk memisahkan objek dengan menambahkan
[no_unique_address]
atribut.tl; dr: Padding mungkin bagian dari objek dan anggota harus dipisahkan.
1 Saya tidak bisa menahan diri untuk tidak menambahkan referensi yang menempati mungkin berarti: Kamus Revisi Unabridged Webster, G. & C. Merriam, 1913 (penekanan milik saya)
Perayapan standar apa yang akan lengkap tanpa perayapan kamus?
sumber
no_unique_address
. Itu membuat situasi sebelum C ++ 20 kurang jelas. Saya tidak mengerti alasan Anda yang mengarah ke "Tidak ada objek yang dapat menempati ruang yang lain jika bukan bersarang di dalamnya" dari basic.life/1.5, khususnya cara mendapatkan dari "penyimpanan tempat benda yang ditempati dilepaskan" untuk "tidak ada objek yang dapat menempati ruang yang lain".