Rozważ następujące trzy struct
s:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
Na typowych platformach, na których int
są 4 bajty, rozmiary, wyrównania i całkowite wypełnienie 1 są następujące:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Przechowywanie elementów blub
i blob
elementów nie zachodzi na siebie , mimo że rozmiar 1 blob
mógłby w zasadzie „pasować” do wypełnienia blub
.
C ++ 20 wprowadza no_unique_address
atrybut, który pozwala sąsiednim pustym członkom na dzielenie tego samego adresu. Pozwala to również wyraźnie na opisany powyżej scenariusz użycia wypełnienia jednego elementu do przechowywania innego. Z preferencji (moje podkreślenie):
Wskazuje, że ten członek danych nie musi mieć adresu odrębnego od wszystkich innych niestatycznych członków danych swojej klasy. Oznacza to, że jeśli element członkowski ma pusty typ (np. Bezstanowy alokator), kompilator może go zoptymalizować, aby nie zajmował miejsca, tak jakby był pustą bazą. Jeśli element członkowski nie jest pusty, wszelkie wypełnienia ogona mogą zostać ponownie wykorzystane do przechowywania innych elementów danych.
Rzeczywiście, jeśli użyjemy tego atrybutu blub b0
, wielkość bla
kropli spadnie do 8
, więc blob
rzeczywiście jest on przechowywany w blub
postaci widocznej na godbolt .
Wreszcie dochodzimy do mojego pytania:
Jaki tekst w standardach (od C ++ 11 do C ++ 20) zapobiega temu nakładaniu się no_unique_address
, w przypadku obiektów, których nie można w prosty sposób skopiować?
Muszę wykluczyć z powyższego obiekty trywialnie kopiowalne (TC), ponieważ w przypadku obiektów TC dozwolone jest przechodzenie std::memcpy
z jednego obiektu do drugiego, w tym podobiektów składowych, a jeśli magazyn byłby nałożony, to by się zepsuło (ponieważ całość lub część magazynu ponieważ sąsiedni członek zostanie zastąpiony) 2 .
1 Obliczamy wypełnienie po prostu rekurencyjnie jako różnicę między rozmiarem struktury a rozmiarem wszystkich jej elementów składowych.
2 Jest to dlaczego mam konstruktory kopia zdefiniowane: zrobić blub
i blob
nie trywialnie copyable .
źródło
Odpowiedzi:
Norma jest strasznie cicha, gdy mówi się o modelu pamięci, i nie bardzo wyraźnie mówi o niektórych terminach, których używa. Ale myślę, że znalazłem działającą argumentację (która może być nieco słaba)
Najpierw dowiedzmy się, co jest nawet częścią obiektu. [basic.types] / 4 :
Reprezentacja obiektowa
b0
składa się zsizeof(blub)
unsigned char
obiektów, czyli 8 bajtów. Bity wypełniające są częścią obiektu.Żaden obiekt nie może zajmować przestrzeni innego obiektu, jeśli nie jest w nim zagnieżdżony [basic.life] /1.5 :
Tak więc czas życia
b0
dobiegałby końca, gdy zajmowana przez nią pamięć byłaby ponownie wykorzystywana przez inny obiekt, tjb1
. Nie sprawdziłem tego, ale myślę, że standardowy nakazuje, aby podobiektyw obiektu, który jest żywy, również powinien być żywy (i nie mogłem sobie wyobrazić, jak to powinno działać inaczej).Więc przechowywania które
b0
okupuje nie mogą być wykorzystywane przezb1
. W standardzie nie znalazłem definicji „okup”, ale myślę, że rozsądną interpretacją byłaby „część reprezentacji obiektu”. W opisie obiektu opisującego cytat używane są słowa „podejmować” 1 . W tym przypadku byłoby to 8 bajtów, więcbla
potrzeba co najmniej jeszcze jednegob1
.Szczególnie w przypadku podobiektów (a więc między innymi elementów danych niestatycznych) istnieje również warunek [intro.object] / 9 (ale zostało to dodane w C ++ 20, thx @BeeOnRope)
(moje podkreślenie) Tutaj znowu mamy problem, że „zajmuje” nie jest zdefiniowane i ponownie argumentowałbym, aby wziąć bajty w reprezentacji obiektu. Zwróć uwagę, że do tego [basic.memobj] / przypisu 29 znajduje się przypis
Co może pozwolić kompilatorowi na przełamanie tego, jeśli może udowodnić, że nie ma zauważalnego efektu ubocznego. Sądzę, że jest to dość skomplikowane w przypadku tak fundamentalnej rzeczy, jak układ obiektu. Być może dlatego ta optymalizacja jest podejmowana tylko wtedy, gdy użytkownik dostarczy informację, że nie ma powodu, aby mieć rozłączne obiekty poprzez dodanie
[no_unique_address]
atrybutu.tl; dr: Wypełnienie może być częścią obiektu, a członkowie muszą być rozłączni.
1 Nie mogłem się oprzeć dodaniu odnośnika, który może zajmować: Webster's Revised Unabridged Dictionary, G. i C. Merriam, 1913 (wyróżnienie moje)
Jakie standardowe indeksowanie byłoby kompletne bez indeksowania w słowniku?
źródło
no_unique_address
. Pozostawia sytuację przed C ++ 20 mniej jasną. Nie zrozumiałem twojego rozumowania prowadzącego do „Żaden obiekt nie może zajmować przestrzeni innego, jeśli nie jest w nim zagnieżdżony” z basic.life/1.5, w szczególności jak uzyskać z „miejsca, które zajmuje obiekt, jest zwolnione” na „żaden obiekt nie może zajmować przestrzeni innego”.