How to set structure element at desired offset
Asked Answered
F

4

6

In embedded programming when describing the hardware one often needs to place struct elements at known predefined positions as the HW engineer designed them. For example, let's define a structure FPGA, which has some 100 registers/areas as in the following simplified example:

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

Now, I am frustrated and angry of manual calculations and possible errors in the case of a change in the structure. Is there any way to do this more robust/convenient? I tried to write it this way:

uchar   Spare2[0xA0 - offsetof(sFPGA, Spare2)];

but this does not compile complaining about incomplete struct... Note, that my example is simplified. In reality there are some 20-30 such spare fields that must be defined - the structure is very big.

Fowle answered 14/8, 2016 at 12:13 Comment(2)
Side note: the spare fields are typically referred to (named as) reserved1, reserved2, etc (at least this is the convention that I've always seen in the code when using a structure in order to represent memory-mapped registers).Wallin
Take a look at en.wikipedia.org/wiki/Data_structure_alignmentDempsey
L
2

Well, this won't win the miss Universe award, but I think it does what you want:

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

Let's say you want N=20 padding fields. You have to add N of those PAD_FIELDS(i, in the beginning of your structure, where i runs for example from 1 to 20 (as in my example) or from 0 to 19 or whatever makes you happy. Then, when you need the padding you add for example , 0x80) which means that the next field will be positioned at offset 0x80 from the beginning of the structure.

Upon running this code, it outputs the following text:

CPLD_Version offset a0
NID_Version offset 10a2

The way this macro works is it defines a structure with your fields, it then incorporates your fields, and adds the padding computed according to the structure.

If you don't mind some boost::preprocessor magic, here's a way you can automate the whole PAD_FIELDS(1,PAD_FIELDS(2,PAD_FIELDS(3,PAD_FIELDS(4,... in the beginning:

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

Notice what changed in the usage:

  1. In the beginning of the structure you write CONTAINS_PADDING(n) where n is the number of padding elements desired.
  2. Right before the end of the structure you have to add a ")".
  3. Instead of ,0x0A) to specify the padding you have to write SET_OFFSET(0x0A); (the ; is optional).
Linked answered 14/8, 2016 at 14:32 Comment(0)
I
1

The language simply doesn't allow you to force particular padding. Even if you add your own padding, the compiler has no idea that's what you're trying to do. It could easily add its own additional padding to align members the way it wants.

Of course, for a particular CPU, OS, and compiler, you might just get lucky in that the padding you add manually might just work out to be the padding you want --- you'd have to write a test program to verify that the offsets of the members are what you think they are.

If you absolutely must access data at specific offsets, you can either try the non-standard __attribute__(packed) gcc extension (but see this); or write custom I/O routes to de/serialize the data from the form it's in into/out-of a struct for easier access at the C programming level.

Ionium answered 14/8, 2016 at 16:20 Comment(2)
Paul: yes, you are absolutely right. I just wanted to simplify the example and omitted the packed attribute which I use constantly in such cases.Fowle
@Fowle That's a really critical piece of information to leave out!Ionium
N
0

What about this:

struct sFPGA {
  struct Internal_S {
    uchar   Spare1[0x24];
    ushort  DiscreteInput;
  } s;
  uchar   Spare2[0xA0 - sizeof (struct Internal_S)];
  ushort  CPLD_Version;
};
Navigator answered 14/8, 2016 at 12:30 Comment(4)
This will work for sure. The problem is that my example is simplified. In reality there are some 20-30 such spare fields that must be defined - the structure is very big...:-(Fowle
@leonp: In this case you might want to write a code generator, which creates the source to the struct in question in preparation to each compilation run.Navigator
_ @alk: Hmm... Any hints/points/links to what you mean? Some more wide explanation? :-)Fowle
@leonp: Use any programming language of your choice to write a program to from a well defined set of rules create the C source defining the struct in question. In short: Write a code generator. :-)Navigator
R
0

Check out this disgusting solution I came up which I think is actually a little cleaner than everything else posted. For my requirements I just needed a handful of registers out of thousands for a device, so I don't have to solve different sized fields in the struct..

 #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;
}
Revolving answered 6/12, 2019 at 0:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.