Структура набивки и упаковки - PullRequest
176 голосов
/ 29 ноября 2010

Рассмотрим:

struct mystruct_A
{
   char a;
   int b;
   char c;
} x;

struct mystruct_B
{
   int b;
   char a;
} y;

Размеры конструкций 12 и 8 соответственно.

Эти конструкции дополнены или упакованы?

Когда происходит заполнение или упаковка?

Ответы [ 8 ]

235 голосов
/ 29 ноября 2010

Заполнение выравнивает элементы структуры по "естественным" границам адреса - скажем, int члены будут иметь смещения, которые mod(4) == 0 на 32-битной платформе. Заполнение включено по умолчанию. Он вставляет следующие «пробелы» в вашу первую структуру:

struct mystruct_A {
    char a;
    char gap_0[3]; /* inserted by compiler: for alignment of b */
    int b;
    char c;
    char gap_1[3]; /* -"-: for alignment of the whole struct in an array */
} x;

Упаковка , с другой стороны, не позволяет компилятору выполнять заполнение - это должно быть явно запрошено - в GCC это __attribute__((__packed__)), поэтому следующее:

struct __attribute__((__packed__)) mystruct_A {
    char a;
    int b;
    char c;
};

создаст структуру размером 6 в 32-битной архитектуре.

Примечание: доступ к памяти без выравнивания медленнее в архитектурах, которые позволяют это (например, x86 и amd64), и явно запрещен для архитектур со строгим выравниванием , таких как SPARC.

36 голосов
/ 07 августа 2016

Я знаю, что этот вопрос старый, и большинство ответов здесь очень хорошо объясняет заполнение, но, пытаясь понять это сам, я подумал, что помогло "визуальное" представление о происходящем.

Процессор считывает память «кусками» определенного размера (слова). Скажем, процессорное слово имеет длину 8 байтов. Он будет смотреть на память как большой ряд 8-байтовых строительных блоков. Каждый раз, когда ему нужно получить некоторую информацию из памяти, он достигает одного из этих блоков и получает его.

Variables Alignment

Как видно на рисунке выше, не имеет значения, где находится символ (длиной 1 байт), поскольку он будет находиться внутри одного из этих блоков, что потребует от процессора обработки только 1 слова.

Когда мы имеем дело с данными размером более одного байта, такими как 4-байтовое int или 8-байтовое двойное число, то, как они выровнены в памяти, влияет на то, сколько слов придется обрабатывать ЦП. Если 4-байтовые блоки выровнены таким образом, что они всегда соответствуют внутренней части блока (адрес памяти кратен 4), нужно обработать только одно слово. В противном случае часть из 4 байтов может иметь часть себя в одном блоке и часть в другом, требуя, чтобы процессор обработал 2 слова для чтения этих данных.

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

Это касается 8-байтового текстового процессора, но эта концепция применима к другим размерам слов.

Заполнение работает, заполняя промежутки между этими данными, чтобы убедиться, что они выровнены с этими блоками, таким образом улучшая производительность при чтении памяти.

Однако, как указано в ответах других, иногда пространство имеет большее значение, чем само исполнение. Возможно, вы обрабатываете много данных на компьютере, на котором недостаточно ОЗУ (можно использовать пространство подкачки, но оно НАМНОГО медленнее). Вы могли бы расположить переменные в программе до тех пор, пока не будет выполнено наименьшее заполнение (как это было значительно проиллюстрировано в некоторых других ответах), но если этого недостаточно, вы можете явно отключить заполнение, что и есть упаковка . *

36 голосов
/ 01 июля 2016

( Приведенные выше ответы объяснили причину довольно ясно, но, кажется, не совсем ясно о размере заполнения, поэтому я добавлю ответ в соответствии с тем, что я узнал из Потерянное искусство упаковки структуры C)


Выравнивание памяти (для структуры)

Правила:

  • Перед каждым отдельным членом,будет заполнение, чтобы оно начиналось с адреса, кратного его размеру.
    Например, в 64-битной системе, int должно начинаться с адреса, кратного 4, и long на 8, shortна 2.
  • char и char[] являются специальными, может быть любым адресом памяти, поэтому им не требуется заполнение перед ними.
  • Для struct, кроме выравниванияДля каждого отдельного элемента размер всей структуры будет выровнен по размеру, кратному размеру наибольшего отдельного элемента, с заполнением в конце.
    Например, если самый большой элемент структуры равен long, то делится на 8, int затем на 4, short, затем на 2.

Order of member:

  • Порядок членов может влиять на фактический размер структуры, так что имейте это в виду.например, stu_c и stu_d из приведенного ниже примера имеют одинаковые члены, но в другом порядке, и приводят к разному размеру для двух структур.

Адрес в памяти (для структуры)

Правила:

  • 64-битная система
    Адрес структуры начинается с (n * 16) байтов.( В приведенном ниже примере видно, что все напечатанные шестнадцатеричные адреса структур заканчиваются на 0. )
    Причина : возможный самый большой отдельный элемент структуры составляет 16 байтов (long double).

Пустое пространство :

  • Пустое пространство между двумя структурами может использоваться неструктурными переменными, которые могут поместиться.
    Например, в test_struct_address() ниже, переменная x находится между смежными структурами g и h.
    . Независимо от того, объявлено ли x, адрес h не изменится, x только что повторно использовал пустое пространство, которое было потрачено g.
    Аналогичный случай для y.

Пример

( для 64-битной системы)

memory_align.c :

/**
 * Memory align & padding - for struct.
 * compile: gcc memory_align.c
 * execute: ./a.out
 */ 
#include <stdio.h>

// size is 8, 4 + 1, then round to multiple of 4 (int's size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long's size),
struct stu_b {
    long l;
    char c;
};

// size is 24, l need padding by 4 before it, then round to multiple of 8 (long's size),
struct stu_c {
    int i;
    long l;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (long's size),
struct stu_d {
    long l;
    int i;
    char c;
};

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double's size),
struct stu_e {
    double d;
    int i;
    char c;
};

// size is 24, d need align to 8, then round to multiple of 8 (double's size),
struct stu_f {
    int i;
    double d;
    char c;
};

// size is 4,
struct stu_g {
    int i;
};

// size is 8,
struct stu_h {
    long l;
};

// test - padding within a single struct,
int test_struct_padding() {
    printf("%s: %ld\n", "stu_a", sizeof(struct stu_a));
    printf("%s: %ld\n", "stu_b", sizeof(struct stu_b));
    printf("%s: %ld\n", "stu_c", sizeof(struct stu_c));
    printf("%s: %ld\n", "stu_d", sizeof(struct stu_d));
    printf("%s: %ld\n", "stu_e", sizeof(struct stu_e));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));

    return 0;
}

// test - address of struct,
int test_struct_address() {
    printf("%s: %ld\n", "stu_g", sizeof(struct stu_g));
    printf("%s: %ld\n", "stu_h", sizeof(struct stu_h));
    printf("%s: %ld\n", "stu_f", sizeof(struct stu_f));

    struct stu_g g;
    struct stu_h h;
    struct stu_f f1;
    struct stu_f f2;
    int x = 1;
    long y = 1;

    printf("address of %s: %p\n", "g", &g);
    printf("address of %s: %p\n", "h", &h);
    printf("address of %s: %p\n", "f1", &f1);
    printf("address of %s: %p\n", "f2", &f2);
    printf("address of %s: %p\n", "x", &x);
    printf("address of %s: %p\n", "y", &y);

    // g is only 4 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "g", "h", (long)(&h) - (long)(&g));

    // h is only 8 bytes itself, but distance to next struct is 16 bytes(on 64 bit system) or 8 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "h", "f1", (long)(&f1) - (long)(&h));

    // f1 is only 24 bytes itself, but distance to next struct is 32 bytes(on 64 bit system) or 24 bytes(on 32 bit system),
    printf("space between %s and %s: %ld\n", "f1", "f2", (long)(&f2) - (long)(&f1));

    // x is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between g & h,
    printf("space between %s and %s: %ld\n", "x", "f2", (long)(&x) - (long)(&f2));
    printf("space between %s and %s: %ld\n", "g", "x", (long)(&x) - (long)(&g));

    // y is not a struct, and it reuse those empty space between struts, which exists due to padding, e.g between h & f1,
    printf("space between %s and %s: %ld\n", "x", "y", (long)(&y) - (long)(&x));
    printf("space between %s and %s: %ld\n", "h", "y", (long)(&y) - (long)(&h));

    return 0;
}

int main(int argc, char * argv[]) {
    test_struct_padding();
    // test_struct_address();

    return 0;
}

Результат выполнения - test_struct_padding():

stu_a: 8
stu_b: 16
stu_c: 24
stu_d: 16
stu_e: 16
stu_f: 24
stu_g: 4
stu_h: 8

Результат выполнения - test_struct_address():

stu_g: 4
stu_h: 8
stu_f: 24
address of g: 0x7fffd63a95d0  // struct variable - address dividable by 16,
address of h: 0x7fffd63a95e0  // struct variable - address dividable by 16,
address of f1: 0x7fffd63a95f0 // struct variable - address dividable by 16,
address of f2: 0x7fffd63a9610 // struct variable - address dividable by 16,
address of x: 0x7fffd63a95dc  // non-struct variable - resides within the empty space between struct variable g & h.
address of y: 0x7fffd63a95e8  // non-struct variable - resides within the empty space between struct variable h & f1.
space between g and h: 16
space between h and f1: 16
space between f1 and f2: 32
space between x and f2: -52
space between g and x: 12
space between x and y: 12
space between h and y: 8

Таким образом, адресом начала для каждой переменной является g: d0 x: dc h:e0 y: e8

enter image description here

20 голосов
/ 19 февраля 2013

Уплотнение структуры подавляет заполнение структуры, заполнение, используемое, когда выравнивание имеет наибольшее значение, упаковка, используемое, когда пространство имеет наибольшее значение.

Некоторые компиляторы предоставляют #pragma для подавления заполнения или для его упаковки в n байтов.Некоторые предоставляют ключевые слова для этого.Обычно прагма, которая используется для изменения заполнения структуры, будет иметь следующий формат (зависит от компилятора):

#pragma pack(n)

Например, ARM предоставляет ключевое слово __packed для подавления заполнения структуры.Прочтите руководство по компилятору, чтобы узнать больше об этом.

Таким образом, упакованная структура - это структура без отступов.

Будут использованы в основном упакованные структуры

  • для экономии места

  • для форматирования структуры данных для передачи по сети с использованием некоторого протокола (конечно, это не очень хорошая практика, потому что вам нужно
    иметь дело с порядком байтов)

5 голосов
/ 29 ноября 2010

Обивка и упаковка - это всего лишь два аспекта одного и того же:

  • упаковка или выравнивание - это размер, до которого округляется каждый элемент
  • отступ - это дополнительное пространство, добавленное для соответствия выравниванию

В mystruct_A при условии, что выравнивание по умолчанию равно 4, каждый элемент выровнен по кратному 4 байтам. Так как размер char равен 1, заполнение для a и c составляет 4 - 1 = 3 байта, в то время как заполнение для int b не требуется 4 байта. Он работает так же для mystruct_B.

1 голос
/ 29 ноября 2010

Упаковка структуры выполняется только тогда, когда вы явно указываете компилятору упаковать структуру. Обивка - это то, что вы видите. Ваша 32-битная система дополняет каждое поле выравниванием слов. Если бы вы сказали своему компилятору упаковать структуры, они бы составляли 6 и 5 байтов соответственно. Не делай этого, хотя. Он не переносимый и заставляет компиляторы генерировать гораздо более медленный (а иногда даже ошибочный) код.

0 голосов
/ 09 марта 2019

Нет ничего об этом! Кто хочет понять предмет должен сделать следующие,

0 голосов
/ 15 августа 2015

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

  1. Чтобы выровнять данныев памяти один или несколько пустых байтов (адресов) вставляются (или остаются пустыми) между адресами памяти, которые выделяются для других элементов структуры во время выделения памяти.Эта концепция называется заполнением структуры.
  2. Архитектура компьютерного процессора такова, что он может одновременно считывать 1 слово (4 байта в 32-разрядном процессоре) из памяти.
  3. Для созданияПри использовании этого преимущества процессора данные всегда выровнены как 4-байтовый пакет, что приводит к вставке пустых адресов между адресами другого члена.
  4. Из-за этой концепции заполнения структуры в C размер структуры всегда не совпадает счто мы думаем.
...