Выравнивание данных и размер структур
От: DiZSl  
Дата: 24.11.19 11:05
Оценка:
Доброго времени суток,

На днях начал разбивать проект на отдельные либы и понял, что не хватает знаний по тому как компилятор выравнивает данные и выделяет память

Допустим у нас есть класс

class CTest
{
private:
int a;
char b;
int c;
};

если я сделаю new CTest() в основном проекте, то как компилятор узнает с каким варавниванием была откомпилирована либа? Ведь выравнивание данных в либе может не совпадать с выравниванием в основном проекте. Тогда может выделиться памяти меньше чем нужно (с разным выравниванием sizeof возвращает 9, 10, 12 байт).
Конечно я могу new CTest() перенести в либу, но проблему это решит только для выделяющихся динамически объектов.

class CTest2
{
public:
CTest2()
{
d=0;
}
private:
CTest2 Test2ж
int d;
};

В дданном примере, если CTest была скомпилирована с выравниванием по 16 байт, а основной проект без выравнивания, то при инициализации d произойдет перезатирание свойств CTest и наоборот

Сейчас поставил #pragma pack для подстраховки.
Но как правильно эта проблема решается?
Re: Выравнивание данных и размер структур
От: Muxa  
Дата: 24.11.19 11:33
Оценка:
DZS>Сейчас поставил #pragma pack для подстраховки.
DZS>Но как правильно эта проблема решается?

так прагма пак ты куда поставил? в публичный хэдер библиотеки, который один и тот же как для приложения, так и для библиотеки.
или у тебя структура там сложнее?
Re: Выравнивание данных и размер структур
От: Pzz Россия https://github.com/alexpevzner
Дата: 24.11.19 12:14
Оценка: +2
Здравствуйте, DiZSl, Вы писали:

DZS>если я сделаю new CTest() в основном проекте, то как компилятор узнает с каким варавниванием была откомпилирована либа?


Никак не узнает.

new, в конечном итоге, превратится в malloc, а malloc ничего такого не знает, и выравнивает по худшему варианту. До последнего времени это было 16 байт, сейчас, с появлением AVX-512, как бы не стало 64.

DZS>Сейчас поставил #pragma pack для подстраховки.

DZS>Но как правильно эта проблема решается?

Не надо ставить #pragma pack без нужны.
Re[2]: Выравнивание данных и размер структур
От: DiZSl  
Дата: 24.11.19 14:08
Оценка:
Здравствуйте, Muxa, Вы писали:

M>так прагма пак ты куда поставил? в публичный хэдер библиотеки, который один и тот же как для приложения, так и для библиотеки.

M>или у тебя структура там сложнее?

в библиотеке завернул все классы в прагму в их хедерах. в основном приложении оставил без изменения

вот так
#pragma pack(push, 8)
class CTest
{
private:
int a;
char b;
int c;
};
#pragma pack(pop)
Re[2]: Выравнивание данных и размер структур
От: DiZSl  
Дата: 24.11.19 14:15
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>new, в конечном итоге, превратится в malloc, а malloc ничего такого не знает, и выравнивает по худшему варианту. До последнего времени это было 16 байт, сейчас, с появлением AVX-512, как бы не стало 64.

Это очевидно, отсюда и вопрос, что делать

Pzz>Не надо ставить #pragma pack без нужны.

Тогда использование либы, выравнивание в которой не совпадает с выравниванием данных в приложении, будет приводить к перезатиранию данных, либо в приложении, либо в самой либе.

Если у либы выравнивание 8, у приложения 1 — то приложение будет перезатирать данные либы при записи в соседние переменные
Если у либы выравнивание 1, у приложения 8 — то все будет работать, т.к. внутри приложения памяти выделится больше чем нужно.

Или я чего-то не понимаю?
Re[3]: Выравнивание данных и размер структур
От: Pzz Россия https://github.com/alexpevzner
Дата: 24.11.19 14:22
Оценка: +2
Здравствуйте, DiZSl, Вы писали:

Pzz>>new, в конечном итоге, превратится в malloc, а malloc ничего такого не знает, и выравнивает по худшему варианту. До последнего времени это было 16 байт, сейчас, с появлением AVX-512, как бы не стало 64.

DZS>Это очевидно, отсюда и вопрос, что делать

Ничего. Жить с этим.

Pzz>>Не надо ставить #pragma pack без нужны.

DZS>Тогда использование либы, выравнивание в которой не совпадает с выравниванием данных в приложении, будет приводить к перезатиранию данных, либо в приложении, либо в самой либе.

Обычно либы собирают либо с дефолтовыми параметрами компилятора, либо явно прописывают в публичном хидере параметры упаковки, если они важны для данной либы.

В общем, если не умничать, на наживешь себе проблем.
Re: Выравнивание данных и размер структур
От: 0x7be СССР  
Дата: 24.11.19 14:24
Оценка:
Здравствуйте, DiZSl, Вы писали:


DZS>Сейчас поставил #pragma pack для подстраховки.

DZS>Но как правильно эта проблема решается?
Так и решается — каждое объявление класса или структуры, в котром выравнивание отличается от дефолтного для архитектуры, надо явно размечать директивами так, чтобы и при сборке и либы и клиентского кода использовались одни и те же значения.

Глобально можно менять этот параметр для всего проекта в настройках компилятора, тогда надо руками следить за тем, чтобы он везде совпадал. Я однажды не уследил и имел несколько часов мозговзрывной отладки, когда у меня в процедуру передавались одни значения, а внутри процедуры "получались" как параметры — другие
Re[3]: Выравнивание данных и размер структур
От: Pzz Россия https://github.com/alexpevzner
Дата: 24.11.19 14:26
Оценка:
Здравствуйте, DiZSl, Вы писали:

Pzz>>new, в конечном итоге, превратится в malloc, а malloc ничего такого не знает, и выравнивает по худшему варианту. До последнего времени это было 16 байт, сейчас, с появлением AVX-512, как бы не стало 64.

DZS>Это очевидно, отсюда и вопрос, что делать

P.S. На линухе malloc() выравнивает на 16 байт, и плевать он хотел на AVX-512.
Re[3]: Выравнивание данных и размер структур
От: Muxa  
Дата: 24.11.19 14:33
Оценка:
DZS>в библиотеке завернул все классы в прагму в их хедерах. в основном приложении оставил без изменения
в смысле?
это же один и тот же файл (должен быть)

// lib.h
#pragma pack(push, 8)
class CTest { ... }
#pragma pack(pop)

// lib.cpp
#include "lib.h"
CTest::CTest() { ... }

// app.cpp
#include "lib.h"
void main() { ... }
Re[4]: Выравнивание данных и размер структур
От: DiZSl  
Дата: 24.11.19 14:45
Оценка:
Здравствуйте, Muxa, Вы писали:

DZS>>в библиотеке завернул все классы в прагму в их хедерах. в основном приложении оставил без изменения

M>в смысле?
M>это же один и тот же файл (должен быть)

M>
M>// lib.h
M>#pragma pack(push, 8)
M>class CTest { ... }
M>#pragma pack(pop)

M>// lib.cpp
M>#include "lib.h"
M>CTest::CTest() { ... }

M>// app.cpp
M>#include "lib.h"
M>void main() { ... }
M>


я имел ввиду, что выравнивание в приложении дефолтное — оно влияет на структуры самого приложения, общий h-ник конечно цепляется из либы. структура такая, как вы и указали выше
Re[5]: Выравнивание данных и размер структур
От: Muxa  
Дата: 24.11.19 15:08
Оценка:
DZS>я имел ввиду, что выравнивание в приложении дефолтное — оно влияет на структуры самого приложения,
Ну и пусть. Главное чтобы приложение знало какого размера структуры в либе, они могут отличаться
Без явного сценария использования я проблем пока что не вижу.
Re[6]: Выравнивание данных и размер структур
От: DiZSl  
Дата: 24.11.19 15:10
Оценка:
Здравствуйте, Muxa, Вы писали:


DZS>>я имел ввиду, что выравнивание в приложении дефолтное — оно влияет на структуры самого приложения,

M>Ну и пусть. Главное чтобы приложение знало какого размера структуры в либе, они могут отличаться
M>Без явного сценария использования я проблем пока что не вижу.

Ок, спасибо
Re: Выравнивание данных и размер структур
От: lis_asm  
Дата: 24.11.19 16:35
Оценка:
Здравствуйте, DiZSl, Вы писали:


DZS>class CTest

DZS>{
DZS>private:
DZS> int a;
DZS> char b;
DZS> int c;
DZS>};

Структура вообще коряво сделана. Без выравниваний гарантировано на чтение числа будет тратиться неско циклов шины. Нужно вначале размещать большие (в данном случае a, c), потом маленькие (в данном случае b). Т.к. все поля максимум по 4 байта, выравниваться будет максимум по 4 (даже если задать 16). Чтоб не было проблем, проще сделать так:

class CTest
{
private:
int a;
int c;
char b;
char reserved[3];
};

Структура будет 12 байт, и никакие выравнивания ее не поменяют.
Re: Выравнивание данных и размер структур
От: Molchalnik  
Дата: 25.11.19 21:02
Оценка:
Здравствуйте, DiZSl, Вы писали:



DZS>если я сделаю new CTest() в основном проекте, то как компилятор узнает с каким варавниванием была откомпилирована либа?

Никак. Подставит значение по умолчанию, если условия компиляции одинаковые, то условие по умолчанию будет совпадать и всё будет работать.
и d произойдет перезатирание свойств CTest и наоборот

DZS>Сейчас поставил #pragma pack для подстраховки.

DZS>Но как правильно эта проблема решается?

есть плюсовые выравнивания, но вообще, ты спрашиваешь, "как правильно забацать платформенно зависимый код на идеологически кросплатформенном языке". правильно — никак, ибо язык кросплатформенный. Но это в теории. а на практике — можешь поставить плюсовыми или сишными(через прагму пак) выравнивание, и это будет работать гарантировано. Впрочем, не на 100%, если компилятор или платформа не поддерживает прописанного тобой выравнивания, то будет упс — она подставит какое-то значение, но уже не факт, что по умолчанию. Но опять же, в одинаковых условиях компиляции это значение у либы и основного кода совпадёт.
Re: Выравнивание данных и размер структур
От: qaz77  
Дата: 26.11.19 08:00
Оценка:
Здравствуйте, DiZSl, Вы писали:
DZS>если я сделаю new CTest() в основном проекте, то как компилятор узнает с каким варавниванием была откомпилирована либа? Ведь выравнивание данных в либе может не совпадать с выравниванием в основном проекте. Тогда может выделиться памяти меньше чем нужно (с разным выравниванием sizeof возвращает 9, 10, 12 байт).

Проблема с различным выравниванием — это частный случай нарушения ODR.
Проблемы могут быть, если заголовок с определением CTest подключается, например, с другим набором определенных макросов
(ну, допустим, такая жесть: #define int double).
Также значения typedef'ов может различаться от набора подключенных выше заголовков и, если они используются в определении — тоже поедут смещения членов.
Причем все это относится не только к "основной проект"/"либа", но и просто для отдельных единиц трансляции (которые, обычно, соответствуют cpp файлам) в рамках одной либы или монолитной программы.

DZS>Конечно я могу new CTest() перенести в либу, но проблему это решит только для выделяющихся динамически объектов.

Не решит это проблему.
Ну создала либа объект CTest со своим выравниванием, но код в основной программе будет обращаться к членам данных (CTest::c) по неправильному смещению.
Re: Выравнивание данных и размер структур
От: TimurSPB Интернет  
Дата: 26.11.19 08:38
Оценка:
DZS>Сейчас поставил #pragma pack для подстраховки.
DZS>Но как правильно эта проблема решается?

Как уже выше писали, надо упаковать.
#pragma pack(push, 1)
class CTest
.....
#pragma pack(pop)


Для подстраховки, везде где ждёшь заданного выравнивания, можно поставить assert
static_assert(alignof(CTest) == 1,    "Запакуй меня!");
static_assert(alignof(CTest::a) == 1, "Запакуй меня!");
static_assert(alignof(CTest::b) == 1, "Запакуй меня!");
static_assert(alignof(CTest::c) == 1, "Запакуй меня!");
Make flame.politics Great Again!
Отредактировано 26.11.2019 8:40 TimurSPB . Предыдущая версия .
Re: Выравнивание данных и размер структур
От: MasterZiv СССР  
Дата: 27.11.19 11:50
Оценка:
Здравствуйте, DiZSl, Вы писали:

DZS>Но как правильно эта проблема решается?


Если у тебя эта структура определена с одним выравниванием, и с другим в разных частях программы (а .lib, .dll и .exe -- это именно такие части),
то это -- нарушение ODR и undefined behavior.
Решение этой проблемы простое -- везде использовать одно и то же определение структур (классов) и с одним и тем же выравниванием.
И для этого надо использовать один и тот же компилятор или совместимые друг с другом компиляторы.

При этом вовсе не обязательно использовать #pragma pack и принудительно паковать плотно.
Достаточно использовать одинаковые определения классов везде, во всех частях (на самом деле просто тупо одно и то же определение из одного и того же заголовка)


DZS> если я сделаю new CTest() в основном проекте, то как компилятор узнает с каким варавниванием была откомпилирована либа? ...


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

DZS> Конечно я могу new CTest() перенести в либу, но проблему это решит только для выделяющихся динамически объектов.


Не имеет значения, какие объекты по классу памяти, динамические или нет.


DZS> В дданном примере, если CTest была скомпилирована с выравниванием по 16 байт, а основной проект без выравнивания, то при инициализации d произойдет перезатирание свойств CTest и наоборот


это UB, на самом деле может произойти всё, что угодно.
Re[2]: Выравнивание данных и размер структур
От: Vaynamond Россия  
Дата: 27.11.19 18:01
Оценка:
Здравствуйте, TimurSPB, Вы писали:

DZS>>Сейчас поставил #pragma pack для подстраховки.

DZS>>Но как правильно эта проблема решается?

TSP>Как уже выше писали, надо упаковать.

TSP>
TSP>#pragma pack(push, 1)
TSP>class CTest
TSP>.....
TSP>#pragma pack(pop)
TSP>


Байтовое выравнивание без нужды лучше не использовать.
Обычно применяется, если нужно из бинарного файла записи прочитать, или пакеты по сети погонять.
Re: Выравнивание данных и размер структур
От: reversecode google
Дата: 27.11.19 21:46
Оценка:
за выравнивание в классах надо сразу бить по ...
то что вы хотите, называется сериализация
и делается она по другому
Re: Выравнивание данных и размер структур
От: Erop Россия  
Дата: 03.12.19 06:48
Оценка:
Здравствуйте, DiZSl, Вы писали:

DZS>Сейчас поставил #pragma pack для подстраховки.

DZS>Но как правильно эта проблема решается?

Лучше всего, что бы настройки с которыми собрана библиотека совпадали или были совместимы с настройками, с которыми собрано приложения.
Выравнивание же есть не только в твоих классах и структурах, но и в STL'ных, например
Ну и кроме настроек выравнивания есть и всякие другие, зависящие от компилятора, платформы и т. д.
Например, из общей библиотеки (dll|so) брать рантайм или из lib. Или к однонитевому рантайму линковаться или многонитевому и т. д.
Соответственно, если у тебя приложение и библиотека прилинкованы к разным версиям рантайма, то у них будут разные реализации operator new/operator delete, не факт, что вообще совместимые между собой. И это вот всё может привести к очень трудноотлаживаемым проблемам.

Поэтому хорошая стратегия такая.
1) Либо ты очень хорошо разбираешься в этих всех вопросах по докам ко всем поддерживаемым платформам/компиляторам и собираешь РАЗНЫЕ версии библиотек под разные настройки,
2) либо ты делаешь всё с дефолтными настройками, как принято на платформе, а то, что они соблюдаются, проверяешь либо в коде инициализации библиотеки либо static_assert'ами.
например, так:
static_assert( sizeof(CTest) == 12, "проверьте опции компиляции! Выравнивание должно быть 16!" )
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.