K_Trenholm
K_Trenholm

Reputation: 504

Copying byte array into a union/struct in C: What is throwing my alignment off?

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!

Upvotes: 0

Views: 1087

Answers (3)

Stephen Ing
Stephen Ing

Reputation: 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()

Upvotes: -1

Kaz
Kaz

Reputation: 58627

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

Upvotes: 0

Jack
Jack

Reputation: 133609

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.

Upvotes: 3

Related Questions