【问题标题】:How to set structure element at desired offset如何在所需的偏移量处设置结构元素
【发布时间】:2016-12-20 21:27:25
【问题描述】:

在嵌入式编程中,当描述硬件时,通常需要将结构元素放置在硬件工程师设计的已知预定义位置。例如,让我们定义一个结构 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 个这样的备用字段 - 结构非常大。

【问题讨论】:

标签: c++ struct offsetof


【解决方案1】:

查看我想出的这个令人作呕的解决方案,我认为它实际上比发布的其他所有内容都要干净一些。对于我的要求,我只需要一个设备的数千个寄存器中的少数几个,所以我不必解决结构中不同大小的字段..

 #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;
}

【讨论】:

    【解决方案2】:

    好吧,这不会赢得环球小姐奖,但我认为它可以满足您的需求:

    #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));
    

    注意用法的变化:

    1. 在结构的开头写上CONTAINS_PADDING(n),其中n 是所需的填充元素数。
    2. 您必须在结构的末尾添加一个“)”。
    3. 而不是 ,0x0A) 来指定您必须编写的填充 SET_OFFSET(0x0A);(; 是可选的)。

    【讨论】:

      【解决方案3】:

      该语言根本不允许您强制使用特定的填充。即使您添加自己的填充,编译器也不知道您正在尝试做什么。它可以轻松地添加自己的附加 填充以按照它 想要的方式对齐成员。

      当然,对于一个特定的 CPU、操作系统和编译器,您可能只是因为您手动添加的填充而幸运可能只是想成为你想要的填充——你必须编写一个测试程序来验证成员的偏移量是你认为的那样。

      如果您绝对必须访问特定偏移量的数据,您可以尝试非标准的__attribute__(packed) gcc 扩展(但请参阅this);或编写自定义 I/O 路由以将数据从其输入/输出到 struct 的表单中反序列化/序列化,以便在 C 编程级别更轻松地访问。

      【讨论】:

      • 保罗:是的,你完全正确。我只是想简化示例并省略了我在这种情况下经常使用的打包属性。
      • @leonp 这是一条非常重要的信息,不容忽视!
      【解决方案4】:

      这个呢:

      struct sFPGA {
        struct Internal_S {
          uchar   Spare1[0x24];
          ushort  DiscreteInput;
        } s;
        uchar   Spare2[0xA0 - sizeof (struct Internal_S)];
        ushort  CPLD_Version;
      };
      

      【讨论】:

      • 这肯定会奏效。问题是我的例子被简化了。实际上,必须定义大约 20-30 个这样的备用字段 - 结构非常大...:-(
      • @leonp:在这种情况下,您可能需要编写一个代码生成器,它会为相关的struct 创建源代码,为每次编译运行做准备。
      • _ @alk:嗯...任何提示/点/链接到你的意思?一些更广泛的解释? :-)
      • @leonp:使用您选择的任何编程语言编写程序,以根据定义明确的规则集创建定义相关struct 的C 源代码。简而言之:编写代码生成器。 :-)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-03
      • 1970-01-01
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多