Рассмотрим следующие три struct
с:
class blub {
int i;
char c;
blub(const blub&) {}
};
class blob {
char s;
blob(const blob&) {}
};
struct bla {
blub b0;
blob b1;
};
На типовых платформах, где int
4 байта, размеры, выравнивания и общее заполнение 1 следующие:
struct size alignment padding
-------- ------ ----------- ---------
blub 8 4 3
blob 1 1 0
bla 12 4 6
Не существует дублирования между хранением элементов blub
и blob
, даже если размер 1 blob
в принципе может «вписаться» в отступы blub
.
C ++ 20 вводит no_unique_address
атрибут, который позволяет соседним пустым элементам использовать один и тот же адрес. Это также явно позволяет описанный выше сценарий использования заполнения одного элемента для хранения другого. Из контекста (акцент мой):
Указывает, что этот член данных не должен иметь адрес, отличный от всех других нестатических членов данных своего класса. Это означает, что если элемент имеет пустой тип (например, Allocator без сохранения состояния), компилятор может оптимизировать его, чтобы он не занимал места, как если бы он был пустой базой. Если элемент не пустой, любой хвостовой отступ в нем также может быть повторно использован для хранения других элементов данных.
В самом деле, если мы используем этот атрибут blub b0
, размер bla
падает до 8
, так что blob
он действительно сохраняется в том blub
виде, в каком это видно на Годболте .
Наконец, мы добрались до моего вопроса:
Какой текст в стандартах (C ++ 11 - C ++ 20) предотвращает это перекрытие no_unique_address
, для объектов, которые нетривиально копируются?
Мне нужно исключить тривиально копируемые (TC) объекты из вышеперечисленного, потому что для объектов TC разрешено переходить std::memcpy
от одного объекта к другому, включая дочерние подобъекты, и если хранилище было перекрыто, это сломалось бы (потому что все или часть хранилища для соседнего члена будет перезаписано) 2 .
1 Мы вычисляем заполнение просто как разницу между размером структуры и размером всех составляющих ее элементов, рекурсивно.
2 Вот почему я определил конструкторы копирования: сделать blub
и blob
не просто копировать .
источник
Ответы:
Стандарт очень тихий, когда речь идет о модели памяти, и не очень четко описывает некоторые термины, которые он использует. Но я думаю, что нашел рабочую аргументацию (которая может быть немного слабой)
Во-первых, давайте выясним, что является частью объекта. [basic.types] / 4 :
Таким образом, объектное представление
b0
состоит изsizeof(blub)
unsigned char
объектов, поэтому 8 байтов. Биты заполнения являются частью объекта.Ни один объект не может занимать пространство другого, если он не является вложенным в него [basic.life] /1.5 :
Таким образом, время жизни
b0
может закончиться, когда занимаемое им хранилище будет повторно использовано другим объектом, т.е.b1
. Я не проверял это, но я думаю, что стандарт требует, чтобы подобъект живого объекта также был живым (и я не мог представить, как это должно работать по-другому).Так хранение , что
b0
занимает не может быть использованоb1
. Я не нашел определения «занимают» в стандарте, но я думаю, что разумная интерпретация будет «частью представления объекта». В представлении описания объекта цитаты используются слова «занять» 1 . Здесь это будет 8 байт, поэтомуbla
нужен как минимум еще один дляb1
.Специально для подобъектов (так что среди прочих нестатических элементов данных) есть также условие [intro.object] / 9 (но это было добавлено в C ++ 20, thx @BeeOnRope)
(выделение мое) Здесь снова у нас есть проблема, которая «не занимает» не определена, и снова я бы поспорил взять байты в представлении объекта. Обратите внимание, что есть сноска к этому [basic.memobj] / footnote 29
Что может позволить компилятору нарушить это, если он может доказать, что наблюдаемого побочного эффекта нет. Я думаю, что это довольно сложно для такой фундаментальной вещи, как макет объекта. Возможно, именно поэтому эта оптимизация выполняется только тогда, когда пользователь предоставляет информацию о том, что нет причин создавать непересекающиеся объекты путем добавления
[no_unique_address]
атрибута.tl; dr: заполнение может быть частью объекта, и члены должны быть непересекающимися.
1 Я не мог удержаться от добавления ссылки, которая занимает, может означать, чтобы заняться: Пересмотренный словарь Вебстера, G. & C. Merriam, 1913 (выделено мной)
Какой стандартный обход будет обходиться без сканирования словаря?
источник
no_unique_address
. Это оставляет ситуацию до C ++ 20 менее ясной. Я не понял ваших рассуждений, приводящих к тому, что «ни один объект не может занимать пространство другого, если он не является вложенным в него» из basic.life/1.5, в частности, как получить из «хранилища, которое занимает объект, освобождено» «ни один объект не может занимать пространство другого».