如何将结构元素设置为所需的偏移量
How to set structure element at desired offset
在嵌入式编程中,当描述硬件时,通常需要将结构元素放置在硬件工程师设计的已知预定义位置。例如,让我们定义一个结构 FPGA,它有 100 个 registers/areas,如下面的简化示例:
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;
};
现在,我对手动计算和在结构变化的情况下可能出现的错误感到沮丧和愤怒。有什么办法可以做到更多robust/convenient?
我试着这样写:
uchar Spare2[0xA0 - offsetof(sFPGA, Spare2)];
但这不会编译抱怨结构不完整...
请注意,我的示例已简化。实际上,必须定义大约 20-30 个这样的备用字段 - 结构非常大。
这个怎么样:
struct sFPGA {
struct Internal_S {
uchar Spare1[0x24];
ushort DiscreteInput;
} s;
uchar Spare2[0xA0 - sizeof (struct Internal_S)];
ushort CPLD_Version;
};
嗯,这不会赢得环球小姐奖,但我认为它符合您的要求:
#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);
(;
是可选的)。
该语言根本不允许您强制使用特定的填充。即使您添加了自己的填充,编译器也不知道您要做什么。它可以很容易地添加自己的 additional 填充以按照 it 想要的方式对齐成员。
当然,对于 特定的 CPU、OS 和编译器,您 可能 幸运的是,您手动添加的填充 可能 只是您想要的填充 --- 您必须编写一个测试程序来验证成员的偏移量是什么你认为他们是。
如果您绝对必须访问特定偏移量的数据,您可以尝试非标准 __attribute__(packed)
gcc 扩展(但请参阅 this);或编写自定义 I/O 路由到 de/serialize 表单中的数据 into/out-of a struct
以便在 C 编程级别更容易访问。
看看我提出的这个令人作呕的解决方案,我认为它实际上比发布的所有其他内容都要干净一些。对于我的要求,我只需要一个设备的数千个寄存器中的少数几个,所以我不必解决结构中不同大小的字段..
#define M(off, name) struct { uint32_t _##name[off]; uint32_t name; };
struct s {
union {
M(0, a)
M(4, b)
};
} __packed;
int main(void)
{
struct s s1;
s1.a = 12;
s1.b = 42;
printf("%u a=%lx\n", s1.a, offsetof(struct s, a));
printf("%u b=%lx\n", s1.b, offsetof(struct s, b));
printf("so=%u\n", sizeof(s1));
return 0;
}
在嵌入式编程中,当描述硬件时,通常需要将结构元素放置在硬件工程师设计的已知预定义位置。例如,让我们定义一个结构 FPGA,它有 100 个 registers/areas,如下面的简化示例:
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;
};
现在,我对手动计算和在结构变化的情况下可能出现的错误感到沮丧和愤怒。有什么办法可以做到更多robust/convenient? 我试着这样写:
uchar Spare2[0xA0 - offsetof(sFPGA, Spare2)];
但这不会编译抱怨结构不完整... 请注意,我的示例已简化。实际上,必须定义大约 20-30 个这样的备用字段 - 结构非常大。
这个怎么样:
struct sFPGA {
struct Internal_S {
uchar Spare1[0x24];
ushort DiscreteInput;
} s;
uchar Spare2[0xA0 - sizeof (struct Internal_S)];
ushort CPLD_Version;
};
嗯,这不会赢得环球小姐奖,但我认为它符合您的要求:
#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);
(;
是可选的)。
该语言根本不允许您强制使用特定的填充。即使您添加了自己的填充,编译器也不知道您要做什么。它可以很容易地添加自己的 additional 填充以按照 it 想要的方式对齐成员。
当然,对于 特定的 CPU、OS 和编译器,您 可能 幸运的是,您手动添加的填充 可能 只是您想要的填充 --- 您必须编写一个测试程序来验证成员的偏移量是什么你认为他们是。
如果您绝对必须访问特定偏移量的数据,您可以尝试非标准 __attribute__(packed)
gcc 扩展(但请参阅 this);或编写自定义 I/O 路由到 de/serialize 表单中的数据 into/out-of a struct
以便在 C 编程级别更容易访问。
看看我提出的这个令人作呕的解决方案,我认为它实际上比发布的所有其他内容都要干净一些。对于我的要求,我只需要一个设备的数千个寄存器中的少数几个,所以我不必解决结构中不同大小的字段..
#define M(off, name) struct { uint32_t _##name[off]; uint32_t name; };
struct s {
union {
M(0, a)
M(4, b)
};
} __packed;
int main(void)
{
struct s s1;
s1.a = 12;
s1.b = 42;
printf("%u a=%lx\n", s1.a, offsetof(struct s, a));
printf("%u b=%lx\n", s1.b, offsetof(struct s, b));
printf("so=%u\n", sizeof(s1));
return 0;
}