Во встроенном программировании при описании аппаратного обеспечения часто необходимо размещать элементы структуры в известных предопределенных позициях, как их разработал инженер HW. Например, давайте определим структуру FPGA, которая имеет около 100 регистров / областей, как в следующем упрощенном примере:
struct __attribute__ ((__packed__)) sFPGA {
uchar Spare1[0x24];
ushort DiscreteInput;
uchar Spare2[0x7A];
//CPLD_Version is required to be at offset 0xA0, so 0xA0-0x24-2=0x7A
ushort CPLD_Version;
};
Теперь я расстроен и зол на ручные расчеты и возможные ошибки в случае изменения структуры. Есть ли способ сделать это более надежным / удобным?
Я попытался написать это так:
uchar Spare2[0xA0 - offsetof(sFPGA, Spare2)];
но это не компилирует жалобы на неполную структуру …
Обратите внимание, что мой пример упрощен. В действительности существует около 20-30 таких запасных полей, которые необходимо определить — структура очень большая.
Ну, это не получит награду Мисс Вселенная, но я думаю, что она делает то, что вы хотите:
#include <boost/preprocessor/cat.hpp>
typedef unsigned char uchar;
typedef unsigned short ushort;
#define PAD_FIELDS(i_,f_, n_) \
typedef struct __attribute__((packed)) {f_} ftype##i_; \
typedef struct __attribute__((packed)) {f_ uchar t_;} ttype##i_; \
f_ uchar BOOST_PP_CAT(padding,i_)[n_ - sizeof (BOOST_PP_CAT(ftype,i_)) * (sizeof (BOOST_PP_CAT(ftype,i_)) != sizeof (BOOST_PP_CAT(ttype,i_)))];
struct sFPGA {
PAD_FIELDS(1,
PAD_FIELDS(2,
uchar Spare1[0x24];
ushort DiscreteInput;
//CPLD_Version is required to be at offset 0xA0
, 0xA0) // First padding
ushort CPLD_Version;
uchar more_stuff[0x50];
ushort even_more[4];
//NID_Version is required to be at offset 0x10A2
, 0x10A2) // Second padding
ushort NID_Version;
} __attribute__((packed));
int main() {
printf("CPLD_Version offset %x\n", offsetof(sFPGA,CPLD_Version));
printf("NID_Version offset %x\n", offsetof(sFPGA,NID_Version));
}
Допустим, вы хотите N = 20 полей заполнения. Вы должны добавить N из этих PAD_FIELDS(i,
в начале вашей структуры, где i
например, от 1 до 20 (как в моем примере) или от 0 до 19, или что угодно, что делает вас счастливым.
Затем, когда вам нужно заполнение, вы добавляете, например, , 0x80)
Это означает, что следующее поле будет расположено со смещением 0x80 от начала структуры.
После запуска этого кода он выводит следующий текст:
CPLD_Version offset a0
NID_Version offset 10a2
Этот макрос работает так, как он определяет структуру с вашими полями, затем включает ваши поля и добавляет заполнение, вычисленное в соответствии со структурой.
Если вы не возражаете против магии boost :: preprocessor, вот способ, которым вы можете автоматизировать весь PAD_FIELDS(1,PAD_FIELDS(2,PAD_FIELDS(3,PAD_FIELDS(4,...
в начале:
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/comma.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/punctuation/paren.hpp>
typedef unsigned char uchar;
typedef unsigned short ushort;
#define PAD_FIELDS(i_,f_, n_) \
typedef struct __attribute__((packed)) {f_} BOOST_PP_CAT(ftype,i_); \
typedef struct __attribute__((packed)) {f_ uchar t_;} BOOST_PP_CAT(ttype,i_); \
f_ uchar BOOST_PP_CAT(padding,i_)[n_ - sizeof (BOOST_PP_CAT(ftype,i_)) * (sizeof (BOOST_PP_CAT(ftype,i_)) != sizeof (BOOST_PP_CAT(ttype,i_)))];
#define PADMAC(z,n,s) PAD_FIELDS BOOST_PP_LPAREN() n BOOST_PP_COMMA()
#define PADREP(n) BOOST_PP_REPEAT(n, PADMAC, junk)
#define FORCE_EVAL(...) __VA_ARGS__
#define CONTAINS_PADDING(n) FORCE_EVAL(PADREP(n)
#define SET_OFFSET(o) BOOST_PP_COMMA() o BOOST_PP_RPAREN()
struct sFPGA {
CONTAINS_PADDING(2);
uchar Spare1[0x24];
ushort DiscreteInput;
//CPLD_Version is required to be at offset 0xA0
SET_OFFSET(0xA0);
ushort CPLD_Version;
uchar more_stuff[0x50];
ushort even_more[4];
//NID_Version is required to be at offset 0x10A2
SET_OFFSET(0x10A2);
ushort NID_Version;
)
} __attribute__((packed));
Обратите внимание, что изменилось в использовании:
CONTAINS_PADDING(n)
где n
количество требуемых элементов заполнения.,0x0A)
чтобы указать отступ вы должны написать SET_OFFSET(0x0A);
( ;
необязательно).Язык просто не позволяет вам форсировать определенные отступы. Даже если вы добавите свой собственный отступ, компилятор понятия не имеет, что вы пытаетесь сделать. Это может легко добавить свой собственный дополнительный отступы для выравнивания членов пути Это хочет.
Конечно, для конкретный Процессор, ОС и компилятор, вы может быть просто повезло в том, что отступы вы добавляете вручную может быть просто работайте так, как вам нужно — вы должны написать тестовую программу, чтобы убедиться, что смещения членов соответствуют вашим ожиданиям.
Если вам абсолютно необходим доступ к данным с определенными смещениями, вы можете попробовать нестандартный __attribute__(packed)
расширение gcc (но смотрите этот); или написать собственные маршруты ввода / вывода для де / сериализации данных из формы, в которой они находятся в / из struct
для более легкого доступа на уровне программирования C.
Как насчет этого:
struct sFPGA {
struct Internal_S {
uchar Spare1[0x24];
ushort DiscreteInput;
} s;
uchar Spare2[0xA0 - sizeof (struct Internal_S)];
ushort CPLD_Version;
};