0

I'm working on some code for a PSoC5LP Microcontroller (ARM Cortex-M3) that takes in an array of bytes from the serial port and save them to a structure full of configuration variables. I seem to be running into a problem with alignment and I can't figure out what the root cause is.

The structure I'm working with (as well as the enums I am using) are defined as follows:

typedef enum{
    MODE_NIGHT = 0x00,
    MODE_DAY = 0x01,
    MODE_DUAL = 0x02
}daymode_t;

typedef enum{
    BRT_MIN = 0x00,
    BRT_MAX = 0x01,
    BRT_CUSTOM = 0x02,
    BRT_RECALL = 0x03
} startbrtmode_t;

typedef enum{
    SWITCH_MIN = 0x00,
    SWITCH_MAX = 0x01,
    SWITCH_RECALL = 0x02,
    SWITCH_CUSTOM = 0x03,
    SWITCH_EQUAL = 0x04
} switchbrtmode_t;

#define BACKLIGHT_CONFIG_SIZE   31
typedef union {
    struct{
        uint16_t        num_steps;          //2
        uint8_t         iadj_day;           //1
        uint8_t         iadj_night;         //1
        float           min_dc_day;         //4
        float           min_dc_night;       //4
        float           max_dc_day;         //4
        float           max_dc_night;       //4
        daymode_t       daymode_default;    //1
        bool            daymode_recall;     //1
        startbrtmode_t  startbrt_default;   //1
        uint16_t        startbrt_step;      //2
        switchbrtmode_t switchbrt_default;  //1
        uint16_t        day_switchstep;     //2
        uint16_t        night_switchstep;   //2
        bool            nonlinear;          //1
    };
    uint8_t bytes[BACKLIGHT_CONFIG_SIZE];
} backlightconfig_t;

The idea being that I can (after validating the reception via CRC) simply memcpy() the received bytes right into the struct thanks to the union:

backlightconfig_t bl_in;
memcpy(bl_in.bytes, message_in, BACKLIGHT_CONFIG_SIZE);

However when I do this, I wind up with some seemingly "shifted" values. I made a spreadsheet for tracking what byte goes where, what I'm receiving, and where it's ending up:

enter image description here

What I'm seeing is that it looks like my bytes after the startbrtmode_t enum are off. My 0x0005 meant for startbrt_step looks like it's getting eaten entirely (I confirmed it is present in the bytes array via debugger, but it doesn't show up in the struct member)? I've had no luck in figuring out why this is happening. I thought it may have been the compiler (ARM GCC 5.4-2016) reserving too much space for the enum, but sizeof(startbrtmode_t) returns 1. I'm actually already doing this with no issues with a different struct/union, but that's a structure filled entirely with single bytes, so I figure it must be something having to do with typing? Any thoughts as to what's going on would be greatly appreciated!

Thanks!

K_Trenholm
  • 482
  • 5
  • 20
  • 1
    Because you don't give the structure in the union any tag name, you can't check what size the structure has. The `enum` types might be bigger than you think; they could be `int` by any other name. There could be other places where there's padding that you didn't expect. I recommend adding a tag (`struct config {` perhaps, instead of just `struct {`), and then you check `sizeof(struct config)` and maybe print `offsetof(struct config, switchbrt_default)` and so on for each of the members. With that information, you will have a better basis for thinking about where the flaw is. – Jonathan Leffler Sep 17 '19 at 21:24
  • Don't use `enum` if you need fine control over the size of the element. Stick with types like `uint8_t` and `uint16_t`. – Barmar Sep 17 '19 at 21:25
  • You may also have padding at the tail of the structure — it will probably be a multiple of 4 bytes to ensure that the `float` members are properly aligned. That may not matter to you. Even without the structure tag, printing `sizeof(backlightconfig_t)` may give you some useful information (it probably won't be 31). – Jonathan Leffler Sep 17 '19 at 21:26
  • @Jonathan Leffler I will definitely try tagging the struct and see what happens, good idea. You're probably right, I may be assuming too much about the size. – K_Trenholm Sep 17 '19 at 23:55
  • @Barmar I had thought enum will allocate the minimum size for the elements of the enum? Is that just a bad assumption I am making? -fshort-enums I thought would ensure this but it didn't seem to do anything unfortunately. – K_Trenholm Sep 18 '19 at 00:01
  • that is not how unions work in the first place. so you are out of spec, if you get it to work great. but if it breaks in the future then dont be surprised. memcpy to the struct not the union. (once you solve the other problems mentioned). Unless the data is from another compile domain then that isnt supported either and a bad idea as well. – old_timer Sep 18 '19 at 02:13
  • I wonder how you have arrived at the number 31. Are you familiar with `sizeof`? – n. m. could be an AI Sep 18 '19 at 05:04
  • 1
    You're asking about [Serialization](https://en.wikipedia.org/wiki/Serialization) and you have fallen into one of the pitfalls of doing serialization with memcpy(). [This answer](https://stackoverflow.com/a/48830425/1401213) has more information. Also search for Serialization to find more advice. – kkrambo Sep 18 '19 at 11:52
  • @old_timer It's common for embedded programming to depend on behaviors that are unspecified or undefined in the spec. Portability is not generally a concern when programming a microcontroller. – Barmar Sep 18 '19 at 15:24

3 Answers3

3

That's because you have no guarantees about how fields are aligned or padded inside a struct. startbrt_step is a uint16_t field so, on some architectures, it may require a 2 bytes alignment for example.

Assuming that offsetof(backlightconfig_t, startbrt_default) == 22 like you assumed, then startbrt_step would start at 23th byte which is not aligned on 2 bytes boundary so a byte of padding is inserted.

--------------------------
 22     startbrt_default
--------------------------
 23     * padding *
--------------------------
 24     startbrt_step
--------------------------

If you put all 2 bytes field after the float fields you could probably solve the problem, but the solution is fragile in any case.

You can enforce alignment to 1 byte boundary with some preprocessor macros but this is not guaranteed to work on some architectures (some ARM abort if you try to access unaligned memory) or it could add performance penalty.

Jack
  • 131,802
  • 30
  • 241
  • 343
  • Nailed it, doing an offsetof() of all the structure members revealed the issue, and it's inserting a padding byte right where you said it would. I believe I can force alignment via preprocessor like you suggest, and see if there's a performance hit I need to worry about. Otherwise I'll just have to parse the members manually from my incoming data. Thanks for the answer! – K_Trenholm Sep 18 '19 at 14:33
0

The mistake in your spreadsheet is that enums do not simply use the narrowest type that will contain their values. According to the GCC manual, the integer type compatible with an enum is unsigned int if there are no negative values, otherwise int.

If using four bytes for a small enum is unacceptable, declare that field with an appropriate integer type instead and just use the enum for the constants that it provides. Be careful that all your constants are in range of that type.

(Modern) C++ allows enum declarations to specify the base integer type that they use, so there is less guesswork; if you want a byte-sized enum, you can ask for that in C++.

Your MODE DFLT variable #20 probably only looks right to you because the system is little endian, and so the least significant byte of the enum is at the right offset. Moreover, you probably tested with zero values of the two variables that follow, MODE RECALL and START BRT DFLT so you didn't notice their effect on MODE DFLT.

If you're not sure about offsets, make a test program which prints the offset and size every structure member:

#include <stdio.h>
#include <time.h>
#include <stddef.h>

/* typeof is a GCC extension */

#define OFSZ(type, memb) (printf("%s: size = %d, offset = %d\n", \
                                 #memb,\
                                 (int) sizeof(typeof(((type *) 0)->memb)),\
                                 (int) offsetof(type, memb)))


int main()
{
  OFSZ(struct tm, tm_year);
  OFSZ(struct tm, tm_sec);
  return 0;
}

$ ./ofs
tm_year: size = 4, offset = 20
tm_sec: size = 4, offset = 0
Kaz
  • 55,781
  • 9
  • 100
  • 149
-1

Try surrounding your union with #pragma pack(1)/#pragma pack()

#pragma pack(1)
typedef union {
    struct{
        uint16_t        num_steps;          //2
        uint8_t         iadj_day;           //1
        uint8_t         iadj_night;         //1
        float           min_dc_day;         //4
        float           min_dc_night;       //4
        float           max_dc_day;         //4
        float           max_dc_night;       //4
        daymode_t       daymode_default;    //1
        bool            daymode_recall;     //1
        startbrtmode_t  startbrt_default;   //1
        uint16_t        startbrt_step;      //2
        switchbrtmode_t switchbrt_default;  //1
        uint16_t        day_switchstep;     //2
        uint16_t        night_switchstep;   //2
        bool            nonlinear;          //1
    };
    uint8_t bytes[BACKLIGHT_CONFIG_SIZE];
} backlightconfig_t;
#pragma pack()
Simson
  • 3,373
  • 2
  • 24
  • 38
  • This is no silver bullet - particularly if unaligned loads/stores are non available (or are, but expensive). If the struct is overlaid on memory mapped IO, it almost certainly won't work as intended. It's not portable either. Explained in other answers to this question. – marko Sep 18 '19 at 08:43