LINUX.ORG.RU

C++ интересно выравнивает память для объектов: зачем и как избежать?

 


0

1

Всем привет!

Как вы думаете, какой размер объекта, в котором одна переменная bool, и одна переменная uint16 (и больше нет других переменных)? Я думал 3 байта. Оказывается 4.

После экспериментов было выяснено, что все объекты выравниваются по размеру наиболее «тяжелого» типа, но не более 4 байт (думаю, зависит от платформы, у меня 32 бит).

Вопросы:
1. Это в нормально или это undefined behaviour и особенность gcc?
2. Как это отключить, и желательно в исходнике (не ключем компилятора)?

Спасибо.

P. S. Эксперимент:

#include<cstdint>
#include<iostream>
#include<iomanip>

class C01{
	bool a;
};
class C02{
	bool a;
	bool b;
};
class C03{
	bool a;
	bool b;
	bool c;
};
class C04{
	uint16_t a;
};
class C05{
	uint16_t a;
	bool b;
};
class C06{
	uint16_t a;
	bool b;
	bool c;
};
class C07{
	uint16_t a;
	bool b;
	bool c;
	bool d;
};
class C08{
	uint32_t a;
};
class C09{
	uint32_t a;
	uint16_t b;
};
class C10{
	uint32_t a;
	uint16_t b;
	uint16_t c;
};
class C11{
	uint32_t a;
	uint16_t b;
	uint16_t c;
	bool d;
};
class C12{
	uint64_t a;
	bool d;
};
class C13{
	uint64_t a;
	uint16_t b;
};
class C14{
	uint64_t a;
	uint16_t b;
	uint16_t c;
	bool d;
};

int main()
{
	std::cout << "Bigget type: bool" << std::endl;
	std::cout << "sizeof(C01)=" << sizeof(C01) << std::endl;
	std::cout << "sizeof(C02)=" << sizeof(C02) << std::endl;
	std::cout << "sizeof(C03)=" << sizeof(C03) << std::endl;
	std::cout << "Bigget type: uint16_t" << std::endl;
	std::cout << "sizeof(C04)=" << sizeof(C04) << std::endl;
	std::cout << "sizeof(C05)=" << sizeof(C05) << std::endl;
	std::cout << "sizeof(C06)=" << sizeof(C06) << std::endl;
	std::cout << "sizeof(C07)=" << sizeof(C07) << std::endl;
	std::cout << "Bigget type: uint32_t" << std::endl;
	std::cout << "sizeof(C08)=" << sizeof(C08) << std::endl;
	std::cout << "sizeof(C09)=" << sizeof(C09) << std::endl;
	std::cout << "sizeof(C10)=" << sizeof(C10) << std::endl;
	std::cout << "sizeof(C11)=" << sizeof(C11) << std::endl;
	std::cout << "Bigget type: uint64_t" << std::endl;
	std::cout << "sizeof(C12)=" << sizeof(C12) << std::endl;
	std::cout << "sizeof(C13)=" << sizeof(C13) << std::endl;
	std::cout << "sizeof(C14)=" << sizeof(C14) << std::endl;
	return 0;
};
Компиляция:
$ g++ -std=c++11 type_test.cpp -o type_test && ./type_test
Реультат:
Bigget type: bool
sizeof(C01)=1
sizeof(C02)=2
sizeof(C03)=3
Bigget type: uint16_t
sizeof(C04)=2
sizeof(C05)=4
sizeof(C06)=4
sizeof(C07)=6
Bigget type: uint32_t
sizeof(C08)=4
sizeof(C09)=8
sizeof(C10)=8
sizeof(C11)=12
Bigget type: uint64_t
sizeof(C12)=12
sizeof(C13)=12
sizeof(C14)=16

P. P. S. static_assert рулит!

★★★★★

Последнее исправление: Kroz (всего исправлений: 1)

Я вообще офигеваю!
Оказывается, в коде внизу C06 и C06_2 имеют разный размер!
Можно это как-то отключить?

class C06{
	uint16_t a;
	bool b;
	bool c;
};
class C06_2{
	bool c;
	uint16_t a;
	bool b;
};

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 2)

Грёбаный стыд

undefined behaviour

малыш, ты прежде чем использовать незнакомые слова, разберись что они означают.

peacelove
()
Ответ на: Грёбаный стыд от peacelove

малыш, ты прежде чем использовать незнакомые слова, разберись что они означают.

Дядя, я сейчас не хочу спорить о терминах. Ты мне лучше расскажи чем это регламентируется и как это отключить.

Kroz ★★★★★
() автор топика

Вставка паддингов для выравнивания, наверное, регламентируется стандартом (иначе не понятно, как работают публичные хедеры).

А отключить паддинги в GCC можно так:

struct { ... } __attribute__((packed));

Если так сделать, то размер структуры будет в точности равен сумме размеров её полей.

intelfx ★★★★★
()
Последнее исправление: intelfx (всего исправлений: 3)
Ответ на: комментарий от Kroz

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

m0rph ★★★★★
()
Ответ на: комментарий от intelfx

Ахахахаха

Вставка паддингов для выравнивания, наверное, регламентируется стандартом

охлол

иначе не понятно, как работают публичные хедеры

удивительно, правда?

peacelove
()
Ответ на: Ахахахаха от peacelove

Ты давай лучше не скатывайся в 5.2, а расскажи, в чём конкретно я тут так сильно не прав (раз это вызывает у тебя приступы истерического смеха).

intelfx ★★★★★
()

Вы имеете дело с выравниванием структур (и простых переменных тоже) в памяти. Обычно каждая переменная выравнивается по своему размеру - 4, 2 или 1 байта. Это нужно по причине того, что так работают процессоры. Доступ к выровненным данным занимает одну инструкцию. Доступ к невыровненным - куча дополнительных инструкций, и то, если компилятор умный. Поэтому структуры рекомендуется составлять определенным образом, чтобы не было «дырок» между их членами. Если же требуется сделать без дырок любой ценой, то структура объявляется «packed».

Deleted
()
Ответ на: комментарий от intelfx

Хотя я догадываюсь. Стандарт, наверное, говорит по этому поводу «implementation-defined», а уже в разных ABI-спеках указываются конкретные алгоритмы. А если ABI ничего не говорит или отсутствует, то тут уже точно implementation-defined и совместимости между компиляторами потенциально никакой. Всё правильно, о великий гуру peacelove?

intelfx ★★★★★
()
Ответ на: комментарий от intelfx

Стандарт, наверное, говорит по этому поводу «implementation-defined»

В точку. «Each non-bit-field member of a structure or union object is aligned in an implementation-defined manner appropriate to its type.»

i-rinat ★★★★★
()
Ответ на: комментарий от Kroz

Конечно разный, если происходит выравнивание на 2 байта, то первая структура это 2 + 1 + 1, т.к. два буля влезают в 16 бит, а вторая: 2 + 2 + 2 (не забываем, что поля структур в памяти располагаются соответственно порядку их определения), т.к. в случае с 1 + 2 + 1(как ты ожидал) &С06_2::a будет не выровнено на 2 байта.

NegatiV
()
Ответ на: комментарий от intelfx

intelfx, m0rph, спасибо за краткие и емкие ответы. Действительно, устраняется __attribute__ или #pragma (по ссылке было описано).

Я еще такое нашел:
http://stackoverflow.com/questions/14510711/how-is-the-size-of-a-c-class-dete...

Kroz ★★★★★
() автор топика
Последнее исправление: Kroz (всего исправлений: 1)
Ответ на: комментарий от Kroz

Я еще такое нашел

А визуализатора выравнивания ты не находил? Что-то такое, что умело бы показывать дырки от выравнивания картинками. Вручную считать влом.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

А визуализатора выравнивания ты не находил?

Не совсем понимаю о чем ты.

Я, в общем-то, принцип понял. Теперь просто там, где мне будет принципиален размер структур, я буду почаще ставить static_assert().

Kroz ★★★★★
() автор топика

Почему невыровненные данные — это плохо:

  • Доступ к ним обычно медленнее, чем к выровненным.
  • Атомарный доступ часто во много раз медленнее.
  • Некоторые процессоры в принципе не поддерживают обращение к невыровненным данным (старые ARMы, например).
  • У тех, что поддерживают, бывают инструкции, которые не (некоторые из SSE).
  • Если поле пересчет границу кэш-лайна, то атомарные операции над ним перестанут быть таковыми.
  • Разные компиляторы по-разному приоретизируют атрибуты выравнивания отдельных полей и упаковки структуры в целом.

Короче, выравнивание придумано не просто так.

Relan ★★★★★
()
Ответ на: комментарий от Deleted

Доступ к невыровненным - куча дополнительных инструкций, и то, если компилятор умный.

Неее, инструкция одна, просто она жрёт разное количество времени в зависимости от того попадает ли то, что она читает/пишет в ширину шины данных. Если не попадает, приходится делать ещё один доступ. Но на современных Икорках это почти не актуально.

GAMer ★★★★★
()
Ответ на: комментарий от Deleted

Ну, как минимум до 486х было именно так, ЕМНИП конечно =)
Из-за чего 8088 и страдал по сравнению с 8086.

GAMer ★★★★★
()
Ответ на: комментарий от Relan

Да, что-то такое я уже читал.

А есть тесты какие-то? Да и вообще, конкретика? Просто я недоверчиво отношусь к словам «во много раз (медленней)», «некоторые (процессоры/инструкции)» и т. п.

Kroz ★★★★★
() автор топика
Ответ на: комментарий от Kroz

Конкретика про инструкции на процессорах: мне сходу вспоминается movaps/movups на x86, можешь на тему них погуглить.

devsdc ★★
()
Ответ на: комментарий от Deleted

2 или 1 байта

ЕМНИП выравниваются по параграфам (это в лучшем случае. А то и по страницам - 4кБ). Но есть особые случаи. Вроде union не выравнивается. Ну и битовые поля (хотя компиляторы нынче умные стали... Может и ровняют).

В любом случае выравнивание по байтам выглядит странно. А про слова уже все забыли.

ziemin ★★
()

Я думал 3 байта. Оказывается 4.

Ты этого не знал, и ты уже чего-то там пытался писать на сплюсе? охлол.

nanoolinux ★★★★
()
Ответ на: комментарий от Kroz

если не выравнено , то вот та часть что по «плохому адресу» будет читаться как минимум не за одно(некоторое время ) а за двухкратное - ибо придётся читать части.

это если сама часть - как бы в пределах размера с которымы оперирует данный проц.

если же это не слово , а много_слов(то бишь явно больше разрядности проца) то деградация в пределах(не более) 2ух.

qulinxao ★★☆
()

OMG! этож сраные основы, которые есть в любой книжке, даже из серии «для полных дебилов»...И потом такие вот орут что сишечка и кресты говно, потому что делают неправильный мед ... 3 байта ... думал он ...

anonymous
()
Ответ на: комментарий от ziemin

А про слова уже все забыли.

У кого Sparc или MIPS, тот все помнит :)

Deleted
()
Ответ на: комментарий от Kroz

Прикинь, а ведь еще разные компиляторы по разному это выравнивание делают ;)

alex-w ★★★★★
()
Ответ на: комментарий от Deleted

А я про MIPS

Ну тогда две инструкции(половинка+половинка/сдвиг), или разгребать unaligned exception, так?

GAMer ★★★★★
()

Ого, тема из рубрики «я познаю мир».

andreyu ★★★★★
()
Ответ на: комментарий от Kroz

где мне будет принципиален размер структур, я буду почаще ставить static_assert().

это очень верный путь.

Deleted
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.