Распределение памяти члена структуры C++
У меня есть структура, которая выглядит так:
struct rtok {
char type;
std::string val;
bool term;
};
Я пишу простой интерпретатор, и эта структура "rtok" - это то, как я представляю токен. У меня есть вектор "rtoks", который я перебираю для создания дерева разбора.
Мой вопрос заключается в том, что если в моей структуре 3 члена и я даю значение только 1 члену, другие члены все равно будут занимать память?
Я имею в виду, что если я установлю "val" равным "test", мой токен займет всего 4 байта или 6 байтов? (4 байта для "val", 1 байт для типа, 1 байт для термина)
4 ответа
Если у вас нет дополнительных членов или виртуальных функций, ваша структура всегда будет занимать sizeof(char) + sizeof(string) + sizeof(bool) + possible padding
, string
часть выделяет себе кусок памяти, который она освобождает при уничтожении. Однако эта память технически не является частью той, которая была выделена для struct
,
Поэтому независимо от того, какие значения вы задаете (или опускаете) для членов, структура всегда будет иметь одинаковый размер.
Не волнуйтесь, это займет значительно больше, чем вы думаете.
Есть два фактора: выравнивание данных и реализация внутреннего типа. Во-первых, о выравнивании данных: все типы в вашей структуре естественно выровнены, что означает, что char
можно по любому адресу, но void*
может потребоваться выравнивание 4 или 8 в зависимости от архитектуры.
Итак, если мы предполагаем, что std::string использует просто char*
внутренне, чтобы держать вас в строке макет на x32 будет:
struct rtok {
char type;
char* val; // here char * for simplicity
bool term;
};
sizeof(rtok)
оператор выдаст 12 байтов, а не 6, и объем памяти будет выглядеть следующим образом:
00: type (one byte)
01: padding
02: padding
03: padding
04-07: char * (4 bytes)
08: term (one byte)
09-0a: padding (3 bytes)
Теперь, если мы заменим char*
с std::string
мы бы обнаружили, что размер структуры вырос, так как sizeof(std::string)
обычно больше, чем 4 байта.
НО, мы не вычислили само строковое значение... И здесь мы попадаем в область управления и распределения кучи.
Память для хранения значения выделяется в куче, и код обычно запрашивает столько, сколько ему нужно, поэтому для строки из 10 символов это будет 11 байтов (10 символов плюс 1 байт для нулевого терминатора).
И куча имеет собственную сложную структуру с кучей небольших блоков и т. Д. На практике это означает, что минимальное потребляемое количество составляет около 16 байтов или более. Эта сумма не то, что вы можете использовать, эта сумма предназначена для управления внутренними структурами кучи, и единственная полезная сумма может быть всего 1 байт.
Если вы сложите все, вы обнаружите, что даже если вы планируете использовать только два символа плюс тип, объем потребляемой памяти будет намного-намного больше.
Данный тип struct
всегда имеет одинаковый размер. Это гарантия от Стандарта. Когда вы определяете struct
, вы говорите: "У меня есть объект такого размера (сумма размеров элементов + возможное заполнение для выравнивания для каждого элемента), и они должны быть в этом порядке в памяти (такой же порядок определений элементов содержится в struct
определение)":
(N4296) 9,2
/ 12 [ Пример: простой пример определения класса
struct tnode {
char tword[20];
int count;
tnode* left;
tnode* right;
};
который содержит массив из двадцати символов, целое число и два указателя на объекты одного типа. [...] - конец примера
/13 Нестатические члены данных (не объединяющего) класса с одинаковым контролем доступа (раздел 11) распределяются так, чтобы более поздние члены имели более высокие адреса в объекте класса. Порядок распределения нестатических элементов данных с различным контролем доступа не определен (раздел 11). Требования выравнивания реализации могут привести к тому, что два смежных элемента не будут выделяться сразу после друг друга; то же самое касается требований к пространству для управления виртуальными функциями (10.3) и виртуальными базовыми классами (10.1).
Обратите внимание на спецификатор "с тем же контролем доступа". Если ваша структура имеет комбинацию элементов данных с различными спецификаторами доступа, компоновка может отличаться от ожидаемой, кроме гарантии, которая дает что-то вроде:
public:
some_type public_1;
private:
some_type private_1;
public:
some_type public_2;
public_2
будет по более высокому адресу, чем public_1
, Помимо этого - не указано. private_1
может быть по более низкому или более высокому адресу.
По поводу вашего другого вопроса (задается в комментариях):
Было бы лучше использовать класс вместо структуры тогда?
В C++ struct
а также class
по существу одинаковы, единственное отличие состоит в том, что члены (и наследование) struct
являются public
по умолчанию, тогда как с class
они есть private
по умолчанию. Это еще яснее в примечании и примере из Стандарта:
§3.1 Объявления и определения [basic.def]
/ 3 [ Примечание: В некоторых случаях реализации C++ неявно определяют конструктор по умолчанию (12.1), конструктор копирования (12.8), конструктор перемещения (12.8), оператор назначения копирования (12.8), оператор назначения перемещения (12.8) или деструктор (12.4) функции-члены. —Конец примечания ] [ Пример: дано
#include <string>
struct C {
std::string s; // std::string is the standard library class (Clause 21)
};
int main() {
C a;
C b = a;
b = a;
}
реализация будет неявно определять функции, чтобы сделать определение C эквивалентным
struct C {
std::string s;
C() : s() { }
C(const C& x): s(x.s) { }
C(C&& x): s(static_cast<std::string&&>(x.s)) { }
// : s(std::move(x.s)) { }
C& operator=(const C& x) { s = x.s; return *this; }
C& operator=(C&& x) { s = static_cast<std::string&&>(x.s); return *this; }
// { s = std::move(x.s); return *this; }
~C() { }
};
- конец примера ]
Обратите внимание, что в примере из стандарта используется struct
а не class
чтобы проиллюстрировать этот момент для не POD structs
, Это еще более ясно, если учесть, что определение struct
в стандарте есть в разделе 9 - "Классы".
Как сказано ранее, struct
всегда фиксированного размера. Есть несколько способов преодолеть это ограничение:
- Сохраните указатель и выделите для него память кучи.
- Использовать "несвязанный" массив
char[1]
в качестве последнего члена, и выделить память дляstruct
сам в кучу. - использование
union
чтобы сэкономить место для перекрывающихся членов.