Как компилятор выделяет память этой структуре?

Я пытался использовать пространства имен и структуры и столкнулся с проблемой.

C++

#include<iostream>
using namespace std;

namespace One
{
 struct Data
 {
 int val;
 char character;
 };
}

namespace Two
{
 struct Data
 {
 int val;
 bool boolean;
 };
}

void functionOne(void)
{
 using namespace One;
 cout << "functionOne()" << endl;
 cout << "The size of struct Data : ";
 cout << sizeof(Data) << endl;
}

void functionTwo(void)
{
 using namespace Two;
 cout << "functionTwo()" << endl;
 cout << "The size of struct Data : ";
 cout << sizeof(Data) << endl;
}

int main()
{
 functionOne();
 functionTwo(); 
} 

Output
functionOne()
The size of struct Data : 8
functionTwo()
The size of struct Data : 8
</iostream>

Пока я меняю код для "пространства имен два" на следующее:

namespace Two
{
 struct Data
 {
 char val;
 bool boolean;
 };
}

Output :

functionOne()
The size of struct Data : 8
functionTwo()
The size of struct Data : 2

Я не могу понять, как компилятор выделяет память для структуры. Заранее спасибо.

2 ответа

Проблема здесь, скорее всего, связана с требованиями к выравниванию. Если я не ошибаюсь, структура выравнивается на основе наибольшего требования к выравниванию ее членов. В первой версии вашей структуры у вас есть int; char; int; char; , Кажется, что на вашем компьютере int выровнены по 4 байта, и поэтому компилятор кладет структуру с дополнительными 3 байтами после символа. Во второй версии у вас есть только bool; char; bool; char; , размер которых равен 1 байту и выровнен по 1 байту (на вашей машине), и поэтому компилятору не нужно набирать ничего, чтобы размер уменьшался до 2.

Я указал "на вашей машине", потому что это может варьироваться в зависимости от нескольких факторов.

Позвольте сделать красивый график!

// One::Data (version 1)
0 4 5 7
[int (size 4), char (size 1), padding (size 3)][...]
// Because of alignment restrictions on int, this needs a padding of 3 bytes

// Two::Data (version 1)
0 4 5 7
[int (size 4), bool (size 1), padding (size 3)][...]
// Because of alignment restrictions on int, this needs a padding of 3 bytes

// One::Data (version 2), no change

// Two::Data (version 2)
0 1 2
[char (size 1), bool (size 1)][...]
// No alignment restrictions, therefore no padding is required


Официальный ответ о том, как компилятор выделяет память, - "как бы он ни желал". Есть несколько ограничений, но не так много. В этом случае, однако, то, что вы видите, вполне логично: многие типы имеют (или могут иметь) ограничения выравнивания и должны быть размещены по адресу, который кратен некоторому значению. И эти ограничения распространяются до любого класса, который содержит членов типа, так как в противном случае вы не могли бы оценить выравнивание члена класса. По-видимому, на вашей машине bool имеет размер 1 (и char должен иметь размер 1), а int имеет размер 4, а также должен быть выровнен по адресу, кратному 4. Таким образом, в One::Data и Two::Data, у вас есть int, за которым следует char или bool, за которым следует достаточное количество байтов заполнения, чтобы сделать общий размер структуры кратным 4. (В принципе, char/bool и дополнение могут быть смешанный в любом порядке, но на практике каждый компилятор, который я видел, помещает заполнение после любых объявлений.)

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

Обратите внимание, что это зависит как от машины, так и от компилятора. На некоторых машинах (например, Sun Sparc или мэйнфрейме IBM) доступ к несогласованному значению вызовет аппаратную ловушку, и компилятор почти обязательно будет выровнять (и вставить дополнение). С другой стороны, на Intel неэффективный доступ будет работать, но с заметным поражением производительности; компиляторы обычно принудительно выравнивают здесь (и для этого требуются оба бинарных API Windows и Linux), но компилятор, возможно, проигнорировал его, и некоторые очень ранние компиляторы Intel сделали это, когда память была намного жестче, чем сейчас. (На самом деле интересный вопрос, который дает максимальную производительность на современной машине. Если у вас большой массив с одной из ваших структур, дополнительные обращения к памяти из-за несоосности будут, вероятно, устранены из кеша или даже из памяти читать конвейер за небольшую дополнительную плату, тогда как меньший размер объекта может привести к уменьшению количества промахов в кеше и, следовательно, к повышению производительности. Но я не предпринимал никаких мер, поэтому я просто догадываюсь.)

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

struct T
{
 ****** d1;
 char c1;
 ****** d2;
 char c2;
};

он будет (обычно) иметь размер 32, где:

struct T
{
 ****** d1;
 ****** d2;
 char c1;
 char c2;
};

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

licensed under cc by-sa 3.0 with attribution.