Выравнивание объектов / структур в C / C ++ - PullRequest
1 голос
/ 20 декабря 2011
#include <iostream>

using namespace std;

struct test
{
    int i;
    double h;
    int j;
};

int main()
{
    test te;
    te.i = 5;
    te.h = 6.5;
    te.j = 10;

    cout << "size of an int: " << sizeof(int) << endl; // Should be 4
    cout << "size of a double: " << sizeof(double) << endl; //Should be 8
    cout << "size of test: " << sizeof(test) << endl; // Should be 24 (word size of 8 for double)

    //These two should be the same
    cout << "start address of the object: " << &te << endl; 
    cout << "address of i member: " << &te.i << endl;

    //These two should be the same
    cout << "start address of the double field: " << &te.h << endl;
    cout << "calculate the offset of the double field: " << (&te + sizeof(double)) << endl; //NOT THE SAME

    return 0;    
}

Выход:

size of an int: 4
size of a double: 8
size of test: 24
start address of the object: 0x7fffb9fd44e0
address of i member: 0x7fffb9fd44e0
start address of the double field: 0x7fffb9fd44e8
calculate the offset of the double field: 0x7fffb9fd45a0

Почему последние две строки дают разные значения? Что-то не так с арифметикой указателей?

Ответы [ 7 ]

8 голосов
/ 20 декабря 2011
(&te + sizeof(double))

Это то же самое, что и

&((&te)[sizeof(double)])

Вы должны сделать:

(char*)(&te) + sizeof(int)
3 голосов
/ 20 декабря 2011

Вы правы - проблема с арифметикой указателя.

Когда вы добавляете к указателю, вы увеличиваете указатель на кратное типу этого указателя

Следовательно, &te + 1 будет 24 байта после &te.

Ваш код &te + sizeof(double) добавит 24 * sizeof(double) или 192 байта.

3 голосов
/ 20 декабря 2011

Во-первых, ваш код неверен, вы хотите добавить размер полей до h (т.е. int), нет оснований предполагать, что double.Во-вторых, вам нужно сначала нормализовать все до char * (арифметика указателей выполняется в единицах того, на что указывается).

В целом, вы не можете полагаться на такой код для работы.Компилятор может свободно вставлять отступы между полями, чтобы выровнять вещи по границам слов и так далее.Если вы действительно хотите узнать смещение определенного поля, есть макрос offsetof, который вы можете использовать.Это определено в <stddef.h> в C, <cstddef> в C ++.

Большинство компиляторов предлагают возможность удалить все отступы (например, GCC __attribute__ ((packed))).


Я считаю, что использование offsetof только для типов POD является четко определенным.
2 голосов
/ 20 декабря 2011

&te + sizeof(double) эквивалентно &te + 8, что эквивалентно &((&te)[8]).То есть - поскольку &te имеет тип test *, &te + 8 добавляет восемь раз размер test.

2 голосов
/ 20 декабря 2011
struct test
{
    int i;
    int j;
    double h;
};

Так как ваш самый большой тип данных составляет 8 байтов, структура добавляет заполнение вокруг ваших целых чисел, либо поместите сначала самый большой тип данных, либо подумайте о заполнении с вашей стороны! Надеюсь, это поможет!

1 голос
/ 20 декабря 2011

Вы можете увидеть, что происходит более четко, с помощью макроса offsetof():

#include <iostream>
#include <cstddef>

using namespace std;

struct test
{
    int i;
    double h;
    int j;
};

int main()
{
    test te;
    te.i = 5;
    te.h = 6.5;
    te.j = 10;

    cout << "size of an int:   " << sizeof(int)    << endl; // Should be 4
    cout << "size of a double: " << sizeof(double) << endl; // Should be 8
    cout << "size of test:     " << sizeof(test)   << endl; // Should be 24 (word size of 8 for double)

    cout << "i: size = " << sizeof te.i << ", offset = " << offsetof(test, i) << endl;
    cout << "h: size = " << sizeof te.h << ", offset = " << offsetof(test, h) << endl;
    cout << "j: size = " << sizeof te.j << ", offset = " << offsetof(test, j) << endl;

    return 0;
}

В моей системе (x86) я получаю следующий вывод:

size of an int:   4
size of a double: 8
size of test:     16
i: size = 4, offset = 0
h: size = 8, offset = 4
j: size = 4, offset = 12

В другой системе (SPARC) я получаю:

size of an int:   4
size of a double: 8
size of test:     24
i: size = 4, offset = 0
h: size = 8, offset = 8
j: size = 4, offset = 16

Компилятор вставляет байты заполнения между членами структуры, чтобы гарантировать правильное выравнивание каждого элемента. Как видите, требования к выравниванию варьируются от системы к системе; в одной системе (x86) double равен 8 байтам, но требуется только 4-байтовое выравнивание, а в другой системе (SPARC) double равен 8 байтам и требует 8-байтовое выравнивание.

Заполнение также может быть добавлено в конце структуры, чтобы гарантировать, что все выровнено правильно, когда у вас есть массив типа структуры. Например, в SPARC компиляция добавляет 4 байта pf-заполнения в конце структуры.

Язык гарантирует, что первый объявленный член будет иметь смещение 0, и что члены располагаются в порядке, в котором они объявлены. (По крайней мере, это так для простых структур; метаданные C ++ могут усложнить ситуацию.)

1 голос
/ 20 декабря 2011

Компиляторы могут свободно распределять struct s, однако они хотят пройти мимо первого члена, и обычно используют отступы для выравнивания границ слов для скорости.

См. Эти:
Несоответствие размеров структуры C
Структура различается по объему памяти?
и др. и др.

...