Отступ в структуре

Я знаю, что есть заполнение в структуре (пример из этого сообщения)

 struct A   -->8 bytes
 {
    char c;
    char d;
 //2 padding here
    int i;
 };
 struct B  -->12 bytes
 {
     char c;
 //3 padding here
    int i;
    char d;
 //3 padding here
 };

Теперь я не понимаю следующий пример:

 typedef struct {  -->**shouldn't it be 12 bytes**
    int a;
    char *str;
 } TestS;

 TestS s;

int main(int argc, char *argv[]) {

   printf("An int is %lu bytes\n", sizeof( int )); -->4
   printf("A Char * is %lu bytes\n", sizeof( char *)); -->8
   printf("A double is %lu bytes\n", sizeof( double )); -->8

   printf("A struct is %lu bytes\n", sizeof s); -->why 16?

   return 0;

 }

Сначала я подумал, что он может выравниваться по 8 * N байтам (поскольку я использую ubuntu-64), поэтому я пробую больше структур.

  typedef struct {
   int i;
   char *str;
  } stru_12;


  typedef struct {
    int i;
    char *str;
    char c;
  } stru_13;

 typedef struct {
    int i;
    char str[7];
 } stru_11;

 typedef struct {
   char *str;
   double d;
 } stru_16;

  stru_12 test12;
  stru_13 test13;
  stru_11 test11;
  stru_16 test16;

int main (int argc, char *argv[]) {
    printf("A test12 is %lu bytes, address is %p\n", sizeof test12, &test12);
    printf("A test13 is %lu bytes, address is %p\n", sizeof test13, &test13);
    printf("A test11 is %lu bytes, address is %p\n", sizeof test11, &test11);
    printf("A test16 is %lu bytes, address is %p\n", sizeof test16, &test16);
}

Результат:

Test12 - это 16 байт, адрес 0x601060

Test13 - 24 байта, адрес 0x601090

Test11 - 12 байт, адрес 0x601080

Test16 - это 16 байт, адрес 0x601070

Извините за то, что так долго.

У меня вопрос:

  • Почему test12 (int + char *) составляет 16 байтов, а test13 (int + char * + char) - 24? (Кажется, что 8 * N предпочтительнее, но разрешено 12 байтов)

  • Почему разница адресов структур составляет 16 единиц адресации (больше заполнения?)?

Для вашего использования:

cache_alignment: 64

размеры адреса: 36 бит физический, 48 бит виртуальный

Ubuntu 14.04.1 LTS x86_64


person Tony    schedule 31.07.2014    source источник
comment
Этот код очень шумный. Не могли бы вы удалить все определения типов и переменные и вместо этого использовать sizeof(struct stru_12) и т. Д.? Меньше визуального беспорядка.   -  person Kerrek SB    schedule 31.07.2014
comment
Спасибо за совет, но мне нужен адрес. Любое решение?   -  person Tony    schedule 31.07.2014
comment
24 == 8 * 3. 8 - это единица выравнивания, а не 12. Разница между адресами бессмысленна, если они не являются адресами элементов одного и того же массива.   -  person n. 1.8e9-where's-my-share m.    schedule 31.07.2014
comment
@Tony, Как правило, каждый член должен быть соответствующим образом выровнен, так что весь объект структуры, потому что он может использоваться в массиве.   -  person Eric Z    schedule 31.07.2014
comment
@Tony: Адрес относительно бессмысленный, поэтому я бы просто не стал с ним беспокоиться, но если вы хотите оставить его как есть, ничего страшного. Это ваш вызов. (И ваш вопрос, конечно!)   -  person Kerrek SB    schedule 31.07.2014


Ответы (2)


Второй вопрос определяется реализацией (и на самом деле, первый, но я покажу вам, почему вы получаете интервал, который получаете независимо). Ваша платформа, по-видимому, 64-битная, и, как таковые, ваши указатели данных также (64-битные). С этим мы заглянем в структуры.


Stru_12

typedef struct 
{
   int i;
   char *str;
} stru_12;

Он выровнен, поэтому str всегда попадает на 8-байтовую границу, включая непрерывную последовательность (массив). Для этого между i и str вводятся 4 байта заполнения.

0x0000 i    - length=4
0x0004 pad  - length=4
0x0008 ptr  - length=8
======================
Total               16

Массив из них всегда будет иметь ptr на 8-байтовой границе при условии, что массив начинается с указанного (что так и будет). Поскольку добавление заполнения между i и str также увеличило размер структуры до кратного 8, никаких дополнительных заполнений не требуется.


Stru_13

Теперь рассмотрим, как это достигается с помощью этого:

typedef struct 
{
    int i;
    char *str;
    char c;
} stru_13;

То же заполнение будет применяться между i и str, чтобы снова разместить str на 8-байтовой границе, но добавление c усложняет ситуацию. Чтобы указатели всегда находились на 8-байтовых границах (включая последовательность / массив этих структур), структуре необходимо заполнение хвостом, но насколько? Что ж, я надеюсь, очевидно, что общий размер структуры должен быть кратен 8, чтобы гарантировать правильное выравнивание любых встроенных указателей (которые также кратны 8). В этом случае добавляются семь байтов хвостового заполнения, чтобы довести размер до 24 байтов:

0x0000 i    - length=4
0x0004 pad  - length=4
0x0008 ptr  - length=8
0x0010 c    - length=1
0x0011 pad  - length=7
======================
Total               24

Stru_13 (часть двух)

Так что попробуйте это. Что вы думаете о тех же полях, которые были у нас раньше, но в другом порядке, в результате получится:

typedef struct 
{
    char *str;
    int i;
    char c;
} stru_13;

Что ж, мы знаем, что хотим str на 8-байтовой границе и i на 4-байтовой границе, и, честно говоря, наплевать на c (всегда горничная невесты):

0x0000 ptr  - length=8
0x0008 i    - length=4
0x000c c    - length=1
0x000d pad  - length=3
======================
Total               16

Запустите это в своей тестовой программе, и вы увидите, что она работает так же, как и мы выше. Он сокращается до 16 байт. Все, что мы сделали, это изменили порядок на более компактный макет, который все еще поддерживает наши требования, и мы уменьшили представление по умолчанию на 8 байтов (одна треть исходной структуры с предыдущим макетом). Сказать, что это важная вещь, которую нужно убрать из всего этого, было бы преуменьшением.

person WhozCraig    schedule 31.07.2014
comment
Это хороший ответ, но что с 12-байтовым случаем test11? - person martin; 31.07.2014
comment
@martin stru_11 не имеет указателей или двойников, поэтому 8-байтовая граница выходит за дверь, но идеальный доступ к адресу i (32-битный int) должен поместить его на 4-байтовую границу и, опять же, поддерживать это в последовательности. Для этого к хвостовой части структуры добавляется еще один дополнительный отступ. В результате получается 12-байтовая длина, и i всегда попадает на 4-байтовую границу (при условии, конечно, что она начинается с одной, что так и будет). Поиграйте с заменой long и short на тип i и посмотрите, что произойдет. - person WhozCraig; 31.07.2014
comment
@martin, также стоит отметить, что вещи могут значительно измениться, когда вы упорядочиваете элементы от наибольшего до наименьшего в своей структуре. С этим особенно стоит поиграть. - person WhozCraig; 31.07.2014
comment
Поэтому он должен убедиться, что все члены структуры могут быть соответствующим образом выровнены в массиве структур, верно? - person Tony; 31.07.2014
comment
@ Тони, дело в том, что тебе не нужно. Компилятор делает это за вас. В большинстве случаев все в порядке. Если вам нужно сжать больше элементов на странице, чтобы лучше использовать строки предварительной выборки и кеширования, найдите время, чтобы внести обоснованные корректировки. Если вы пишете свежий код, в идеале возьмите за привычку упорядочивать вещи так, чтобы они имели достойное компактное представление, но не позволяйте преждевременной оптимизации испортить во всем остальном идеально хороший рабочий день. Кнут даст вам пощечину на всем пути из Стэнфорда, если вы это сделаете. - person WhozCraig; 31.07.2014
comment
Спасибо за ваш ответ и комментарии. Один неуместный вопрос: говорит ли Кнут что-нибудь об этом в TAOCP? (такой интересный аромат :) - person Tony; 31.07.2014
comment
Если вы имеете в виду цитату о том, что преждевременная оптимизация является корнем всех зол, то нет. Это было из статьи, которую он написал в 1974 году: Структурированное программирование с помощью операторов GOTO , стр. 268 (как это для иронии). - person WhozCraig; 31.07.2014
comment
Большое спасибо за вашу ссылку и терпение. - person Tony; 31.07.2014

Указатели должны быть правильно выровнены, чтобы ЦП мог их использовать.

В C / C ++ структуры должны работать в массивах, поэтому конец структуры дополняется в этом отношении.

struct A
{
    char a;
    // 7 bytes of padding
    char *p;
    char b;
    // 7 bytes of padding
};

A array[3];  // the last padding is important to do this

В такой структуре p должен быть выровнен, чтобы процессор мог читать указатель без генерации ошибки (32-битные процессоры INTEL могут быть настроены без ошибок для невыровненных данных, но это не очень хорошая идея: он медленнее и часто пропускает об ошибках, которые являются ошибками. 64-битные процессоры имеют больше ограничений в этой области.)

Итак, поскольку вы используете 64-битную версию, размер указателя составляет 8 байтов, а выравнивание непосредственно перед указателем должно быть кратным 8.

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

На самом деле есть только 2 случая, когда вы должны беспокоиться об этом: (1) создание структуры для сохранения в файле и (2) создание структуры, которую вы будете размещать в очень большом количестве. Во всех остальных случаях не беспокойтесь об этом.

person Alexis Wilke    schedule 31.07.2014