@Kash_Tan

Как хранится c++ struct в памяти и как определить размер вручную?

Я знаю что struct хранится в стеке с помощью некого alignment-а, но никак не понимаю что это и как работает, и нужен поэтапаный алгоритм определения размера.
  • Вопрос задан
  • 412 просмотров
Пригласить эксперта
Ответы на вопрос 6
mayton2019
@mayton2019
Bigdata Engineer
Скорее всего такого алгоритма нет. Размер структуры будет зависеть
от разрядности платформы и от типа компиллятора и возможно от всяких
опций типа #pragrma pack. Тоесть получается нехилый квест.

Технически, тебе достаточно sizeof для решения твоих прикладных задач.
Ответ написан
@Mercury13
Программист на «си с крестами» и не только
Выяснилось, что на компьютерах с разрядностью 16 и более бит минимальной единицей памяти стоит делать всё равно 8-битный байт: это сильно упрощает работу с узкими типами данных, как в памяти, так и на вводе-выводе. Например, канал RGB8 или кодовая единица UTF-8 — байт. Считаем для простоты, что разрядность 16, порядок Intel.

Чтобы загрузить 2-байтовое число в 2-байтовый регистр, есть ДВА варианта.
1. ОЗУ, кэш и другие устройства хранения передают нижний байт данных на нижний байт шины, верхний, соответственно, на верхний.
2. Байты, чьи номера кратны двум, соединяются только с нижним байтом шины, не кратные — только с верхним. А уж процессор как-то разбирает, что делать, если двухбайтовое число сидит по нечётному адресу и считывается за два обращения к шине.

Более простым оказался второй подход: он упрощает схемотехнику всех сидящих на шине, кроме процессора. В частности, дешифраторы становятся на 1 бит меньше, и упрощается топология. А процессор — в нём и так микропрограммное управление, и в нём и так до хрена соединений, и если обратился к байту (!) по адресу условно 4, вероятно, скоро потребуется адрес 5 и его один хрен стоит закэшировать.

Но это значит, что доступ к 2-байтовым числам по нечётному адресу (1-2, 3-4, 5-6) будет медленнее, чем по чётному (0-1, 2-3, 4-5). И потому по умолчанию двухбайтовые числа располагаются именно по ЧЁТНЫМ адресам, и структура { char a; short b; char c; } будет иметь длину 6 байт (0 — a, 1 — выравнивание, 2,3 — b, 4 — c, 5 — выравнивание), а структура { short b; char a; char c; } — всего 4 байта (всё упаковано плотно).

Но это приводит к двум вещам.
1. Если нужно сочетать скорость и компактность, надо чётко выравнивать структуры данных. Обычно подобная ручная оптимизация делается для мелких частых структур.
2. Если кусок памяти подготавливается в памяти, а потом отправляется на устройство, надо принудительно отключить выравнивание. Если в предыдущем примере формат файла так устроен, что байт 0 — a, байты 1 и 2 — b, байт 3 — c, то можно написать
#include <iostream>

struct Q1 { char a; short b; char c; };
struct __attribute__ ((packed)) Q2 { char a; short b; char c; };

int main()
{
    std::cout << sizeof(Q1) << ' ' << sizeof(Q2) << '\n';   // 6 4
}

Q1 (или более оптимизированную short-char-char) использовать для хранения в памяти, а Q2 — для записи в файл, раз уж у него такой формат.

Да, почему компилятор сам этого не делает? Это нарушает совместимость компиляторов. В Си++ до 20 включительно он имел право кое-что переставить, если в struct/class были поля с разными правами доступа (но никто по факту не делал), в 23+ больше не имеет права.

Ну а алгоритм прост.
1. Длина := 0, выравнивание := 1
2. Для каждого поля: позиция := длина, округлённая по выравниванию[i]; длина := позиция+длина[i]; выравнивание := max{выравнивание; выравнивание[i]}
3. Общая длина := длина, округлённая по общему выравниванию

Существует очень простой способ уменьшить выравнивание: отсортировать поля по убыванию выравнивания.

Для того же Q1:
0. Длина = 0, выравнивание = 1
1. char a: длина округляется до 0, затем 1, выравнивание = 1
2. short b: длина округляется до 2, затем 4, выравнивание = 2
3. char c: длина округляется до 4, затем 5, выравнивание = 2
4. Общая длина округляется до 6. Ну а выравнивание = 2
Ответ написан
Комментировать
jcmvbkbc
@jcmvbkbc
"I'm here to consult you" © Dogbert
поэтапаный алгоритм определения размера

Если откинуть тонкости связанные с битовыми полями, то алгоритм простой. Обозначим выравнивание всей структуры A, размер всей структуры S, размер головы структуры из i полей Si, размер i-го поля SFi, выравнивание i-го поля AFi, операцию округления X вверх до ближайшего кратного Y R(X, Y) (т.е., например, R(4, 4) = 4, R(5, 4) = 8), количество полей структуры n. Тогда

S0 = 0
Si = R(Si - 1, AFi) + SFi
A = maxi = 1..n(AFi)
S = R(Sn, A)

Второй и третий шаги гарантируют, что в выравненной структуре каждое поле тоже выравнено.
Четвёртый шаг гарантирует, что если такие структуры поместить в массив, то все элементы массива будут одинаково выравнены.

Разумеется всю эту математику имеет смысл делать если пишешь компилятор, во всех остальных случаях размеры и выравнивания структур можно узнать непосредственно у компилятора.
Ответ написан
Комментировать
@vanyamba-electronics
typedef struct {
int a;
char b;
} Struct;

Struct s1;
cout << hex << &(s1.a) << endl;
cout << &(s1.b) << dec << endl;
Ответ написан
Комментировать
@yatanai
Поклонник православного С
sizeof возвращает тебе размер в байтах, которые могут иметь разную разрядность на разных платформах, но в основном 8. Потому это надёжный способ определять какой реальный объём занимает структура.
struct pack
{
    short x;
    int y;
    short z;
};
//...
auto size = sizeof(pack) //вернёт 12 байт на AMD64


Выравнивание переменных можно изменить используя РАЗНЫЕ ключевые слова, такие как alignas или #pragma pack(). Первое поможет выравнять данные по большей границе чем есть на самом деле, а второе наоборот.
#pragma pack(push, 1)
struct pack
{
    short x;
    int y;
    short z;
};
#pragma pack(pop)
struct pack2
{
    alignas(16) short x;
    alignas(16) int y;
    short z;
};
//...
auto size = sizeof(pack) //вернёт 8 байт
auto size = sizeof(pack2) //вернёт 32 байта


Гугление паков и алигментов объяснит тебе тонкости более подробно
Ответ написан
Комментировать
alex1951
@alex1951
"умных преподавателей слушал я невнимательно..."
Можно почитать "Искусство упаковки структур в С" - https://tproger.ru/translations/art-of-structure-p...
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы