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


считаем:

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

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

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

эти структуры проложены или упакованы?

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

7 158

7 ответов:

обивкавыравнивает члены структуры к" естественным " границам адреса-говорят,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.

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

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

Variables Alignment

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

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

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

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

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

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

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

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

#pragma pack(n)

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

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

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

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

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

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


правила выравнивания памяти - для структуры

правила:

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

порядок членов:

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

адрес в памяти для struct

правила:

  • 32 битовая система
    Начинается с (n * 8) байт.
    причина: самый большой отдельный член структуры составляет 8 байт.
  • 64 битной системе
    Начинается с (n * 16) байт.
    причина: самый большой отдельный член структуры составляет 16 байт.

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

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

пример

(для 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", "g", "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 g and y: 8

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

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

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

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

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

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