Пустая оптимизация данных: возможно ли это?

В С++ большинство оптимизаций выведены из правила as-if. То есть, пока программа ведет себя так: если оптимизация не состоялась, то они действительны.

Оптимизация пустых баз данных является одним из таких трюков: в некоторых условиях, если базовый класс пуст (не имеет какого-либо нестатического элемента данных), то компилятор может лидировать в представлении своей памяти.

По-видимому, кажется, что стандарт запрещает эту оптимизацию для членов данных, то есть даже если элемент данных пуст, он все равно должен занимать не менее одного байта: от n3225, [class]

4 - Завершенные объекты и субобъекты класса типа класса должны иметь ненулевой размер.

Примечание. Это приводит к использованию частного наследования для разработки политики, чтобы при необходимости активировать EBO

Мне было интересно, можно ли с помощью правила as-if выполнить эту оптимизацию.


изменить: следуя нескольким ответам и комментариям, а также уточнить, что мне интересно.

Во-первых, позвольте мне привести пример:

struct Empty {};

struct Foo { Empty e; int i; };

Мой вопрос: почему sizeof(Foo) != sizeof(int)? В частности, если вы не указали какую-либо упаковку, вероятность того, что Foo будет в два раза больше, чем количество int, должно быть связано с проблемами выравнивания, что кажется смехотворным завышенным.

Примечание: мой вопрос не в том, почему это sizeof(Foo) != 0, на самом деле это не требуется EBO либо

В соответствии с С++ это происходит потому, что не может быть нулевого размера. Однако базе разрешено иметь нулевой размер (EBO), поэтому:

struct Bar: Empty { int i; };

вероятно, (благодаря EBO) подчиняться sizeof(Bar) == sizeof(int).

Стив Джессоп, похоже, придерживается мнения, что это так, что ни один из двух под-объектов не будет иметь одинаковый адрес. Я думал об этом, однако в большинстве случаев это фактически не мешает оптимизации:

Если у вас есть "неиспользуемая" память, то это тривиально:

struct UnusedPadding { Empty e; Empty f; double d; int i; };
// chances are that the layout will leave some memory after int

Но на самом деле это даже "хуже", чем это, потому что пространство Empty никогда не записывается (вам лучше не делать, если EBO вступает...), и поэтому вы можете разместить его на занятом месте, не является адресом другого объекта:

struct Virtual { virtual ~Virtual() {} Empty e; Empty f; int i; };
// most compilers will reserve some space for a virtual pointer!

Или, даже в нашем исходном случае:

struct Foo { Empty e; int i; }; // deja vu!

Можно было бы (char*)foo.e == (char*)foo.i + 1, если бы все, что мы хотели, было другим адресом.

Ответы

Ответ 1

В соответствии с правилом as-if:

struct A {
    EmptyThing x;
    int y;
};

A a;
assert((void*)&(a.x) != (void*)&(a.y));

Утверждение не должно запускаться. Поэтому я не вижу никакой пользы в тайном создании x размера 0, когда вам просто нужно добавить дополнение к структуре в любом случае.

Я предполагаю, что в теории компилятор мог бы отслеживать, могут ли указатели быть приняты к членам, и сделать оптимизацию только в том случае, если они определенно не являются. Это было бы ограниченным использованием, так как было бы две разные версии структуры с разными макетами: одна для оптимизированного случая и одна для общего кода.

Но, например, если вы создаете экземпляр A в стеке и делаете что-то с ним, которое полностью встроено (или иначе видимо для оптимизатора), да, части структуры могут быть полностью опущены. Однако это не относится к пустым объектам, но пустой объект является лишь частным случаем объекта, к хранилищу которого не обращаются, и поэтому в некоторых ситуациях он никогда не может быть выделен вообще.

Ответ 2

С++ по техническим причинам указывает, что пустые классы должны иметь ненулевой размер.
Это означает, что отдельные объекты имеют разные адреса памяти. Поэтому компиляторы молча вставляют байт в "пустые" объекты.
Это ограничение не относится к частям базового класса производных классов, поскольку они не являются свободными.

Ответ 3

Поскольку Empty является POD-типом, вы можете использовать memcpy для перезаписи своего "представления", поэтому лучше не делиться им с другим объектом С++ или полезными данными.

Ответ 4

Учитывая struct Empty { };, рассмотрим, что произойдет, если sizeof(Empty) == 0. Общий код, который выделяет кучу для пустых объектов, может легко вести себя по-другому, например, - realloc(p, n * sizeof(T)), где T - Empty, тогда эквивалентен free(p). Если sizeof(Empty) != 0, то такие вещи, как memset/memcpy и т.д., Будут пытаться работать с областями памяти, которые не были использованы объектами Empty. Таким образом, компилятор должен будет сшивать такие вещи, как sizeof(Empty) на основе возможного использования значения - это кажется мне почти невозможным.

Отдельно, при текущих правилах С++ уверенность в том, что каждый член имеет отдельный адрес, означает, что вы можете использовать эти адреса для кодирования некоторого состояния об этих полях - например, имя текстового поля, следует ли посещать какую-либо функцию-член объекта поля и т.д. Если адреса внезапно совпадают, любой существующий код, зависящий от этих ключей, может сломаться.