Reputation: 504
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:
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
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
Reputation: 58627
The mistake in your spreadsheet is that enum
s 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
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