Что такое "упакованная" структура в C?
Я собираюсь написать код C, написанный для компилятора Microchip C30, и часто вижу структуры, определенные следующим образом:
typedef struct __attribute__((__packed__))
{
IP_ADDR MyIPAddr; // IP address
IP_ADDR MyMask; // Subnet mask
IP_ADDR MyGateway; // Default Gateway
// etc...
} APP_CONFIG;
Что значит упакованный?
6 ответов
Когда структуры определены, компилятору разрешается добавлять отступы (пробелы без фактических данных), чтобы члены попадали в границы адресов, которые легче доступны для ЦП.
Например, на 32-разрядном процессоре 32-разрядные элементы должны начинаться с адресов, кратных 4 байтам, для эффективного доступа (чтения и записи). Следующее определение структуры добавляет 16-битное заполнение между обоими элементами, так что второй элемент попадает в правильную границу адреса:
struct S {
int16_t member1;
int32_t member2;
};
Структура в памяти вышеупомянутой структуры в 32-битной архитектуре (~ = заполнение):
+---------+---------+
| m1 |~~~~| m2 |
+---------+---------+
Когда структура упакована, эти отступы не вставляются. Компилятор должен генерировать больше кода (который работает медленнее) для извлечения невыровненных элементов данных, а также для записи в них.
Та же самая структура, когда она упакована, появится в памяти как-то так:
+---------+---------+
| m1 | m2 |~~~~
+---------+---------+
Он указывает компилятору не добавлять какие-либо отступы между членами struct
,
Смотрите, например, эту страницу.
Позвольте мне объяснить концепцию заполнения в структурах, а затем упакованных структур на примере.
А потом давайте посмотрим, почему требуется упаковка.
Обивка:
struct eg_struct
{
unsigned char abc;
unsigned int xyz;
}
Когда структура объявлена как выше для 16-битной архитектуры, переменная abc
будет назначен какой-то адрес. Следующий адрес не назначен переменной xyz
вместо этого добавляется один дополнительный байт, а затем следующий адрес будет назначен переменной xyz
,
В итоге структура выглядит примерно так:
struct eg_struct
{
unsigned char abc;
unsigned char paddedbytes[1];
unsigned int xyz;
}
Заполнение делает адреса переменных-членов легко доступными для микроконтроллера. Недостатком являются лишние ненужные байты, которые входят в картинку.
Упаковка:
Если та же структура объявлена с использованием атрибута "packed
”, Дополнительный байт не будет добавлен после переменной abc
,
Позвольте мне привести один пример, где необходима упаковка:
Рассмотрим микроконтроллер с интерфейсом EEPROM, в котором хранится некоторая структура.
Представьте, что запись функции в EEPROM будет выглядеть следующим образом:
Write_EEPROM(EEPROM address, Ram address, Byte count);
Теперь, если упаковка не завершена, дополнительные заполненные байты будут занимать место в EEPROM, что бесполезно.
Одна вещь, которая не была явно вызвана, - то, что упаковка обычно делается, чтобы соответствовать предопределенным структурам поля. Например, на низкоуровневом уровне сетевого интерфейса между сетевыми машинами происходит обмен серией байтов. После получения данных их необходимо сопоставить с высокоуровневой структурой, чтобы можно было легко манипулировать данными. Это когда обычно не требуется заполнения, чтобы структура напрямую отображалась в байтах.
Обмен сетевыми данными также связан с проблемой байтовой байтовости (т. Е. Почти все сетевые данные используют формат байтов с прямым порядком байтов, независимо от порядкового номера машин источника и назначения).
Кроме того, некоторые машины не могут получить доступ к широким данным по невыровненному адресу, например, ядра Cortex-M0 не могут получить доступ к 32-разрядным данным по не 32-разрядному выровненному адресу, поэтому в таких случаях необходимо соблюдать осторожность при написании сетевого кода.
_attribute__((__packed__))
означает (наиболее вероятно) "не вставлять какие-либо отступы, чтобы сделать вещи быстрее", а также может означать "не вставлять какие-либо выравнивания, чтобы сохранить выравнивание".
При использовании упаковки во время объявления структуры компилятор не будет добавлять какие-либо отступы к членам одной и той же структуры. Ниже приведен пример кода и вывод, который говорит сам за себя.
$ cat structure_packed.c
#include <stdio.h>
typedef struct __attribute__((__packed__))
{
char a;
int ai;
char ac;
}A;
struct B
{
char b;
int bi;
char bc;
};
int main()
{
A a;
struct B b;
int c;
printf("size of struct A: %lu, addr a: %p, addr ai: %p, addr ac: %p\n", sizeof(a), &(a.a), &(a.ai), &a.ac);
printf("size of struct B: %lu, addr b: %p, addr bi: %p, addr bc: %p\n", sizeof(b), &(b.b), &(b.bi), &b.bc);
printf("addr of c: %p\n", &c);
return 0;
}
Компиляция
$ gcc структура_упакована.c -o структура_упакована
Выполнить|Вывод
$ ./структура_упакована
размер структуры A: 6, адрес a: 0x7ffc6f177ed6, адрес ai: 0x7ffc6f177ed7, адрес ac: 0x7ffc6f177edb
размер структуры B: 12, адрес b: 0x7ffc6f177edc, адрес bi: 0x7ffc6f177ee0, адрес bc: 0x7ffc6f177ee4
адрес c: 0x7ffc6f177ed0