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

Рассмотрим:

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

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

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

Являются ли эти структуры дополненными или упакованными?

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

Ответ 1

Прокладка выравнивает членов структуры к "естественным" границам адреса - скажем, 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 it __attribute__((__packed__)), поэтому следующее:

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

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

Заметьте, что - неприглаженный доступ к памяти медленнее на архитектурах, которые позволяют это (например, x86 и amd64), и явно запрещен для строгих архитектур выравнивания, таких как SPARC.

Ответ 2

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


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

Правила:

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

Порядок участника:

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

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

Правила:

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

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

  • Пустое пространство между двумя структурами может использоваться неструктурными переменными, которые могут вписываться.
    например, в 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 size),
struct stu_a {
    int i;
    char c;
};

// size is 16, 8 + 1, then round to multiple of 8 (long 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 size),
struct stu_c {
    int i;
    long l;
    char c;
};

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

// size is 16, 8 + 4 + 1, then round to multiple of 8 (double 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 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

Ответ 3

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

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

Variables Alignment

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

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

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

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

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

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

Ответ 4

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

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

#pragma pack(n)

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

Таким образом, упакованная структура представляет собой структуру без заполнения.

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

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

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

Ответ 5

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

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

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

Ответ 6

Структурное дополнение добавляет дополнительные биты в конце структуры, так что структура завершает границу слова. В "Отлож. Структуры" вы найдете больше об избегании добавления структуры.

Ответ 7

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

Ответ 8

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

Ответ 9

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

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