Подплата в структура

Знам, че има padding in struct (пример от тази публикация)

 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

Тест13 е 24 байта, адресът е 0x601090

Тест11 е 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
Този код е много шумен. Можете ли да премахнете всички typedefs и променливите и вместо това да използвате 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-байтова граница, включително в непрекъсната последователност (масив). За да направите това, 4 байта запълване се въвеждат между i и str.

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 (part deux)

Така че опитайте това. Какво може да мислите, че същите полета, които имахме преди, но подредени различно, ще доведат до:

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