将字节数组复制到 C 中的 union/struct 中:是什么让我的对齐中断?

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

我正在为 PSoC5LP 微控制器 (ARM Cortex-M3) 编写一些代码,该微控制器从串口接收字节数组并将它们保存到充满配置变量的结构中。我似乎 运行 遇到对齐问题,我无法弄清楚根本原因是什么。

我正在使用的结构(以及我正在使用的枚举)定义如下:

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;

想法是我可以(在通过 CRC 验证接收后)简单地 memcpy() 将接收到的字节直接放入结构中,这要归功于联合:

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

然而,当我这样做时,我得到了一些看似 "shifted" 的值。我制作了一个电子表格来跟踪什么字节去了哪里,我收到了什么,以及它在哪里结束:

我看到的是 startbrtmode_t 枚举关闭后我的字节看起来像。我的 0x0005 意味着 startbrt_step 看起来它被完全吃掉了(我通过调试器确认它存在于字节数组中,但它没有出现在结构成员中)?我没有运气弄清楚为什么会这样。我认为可能是编译器(ARM GCC 5.4-2016)为枚举保留了太多 space,但是 sizeof(startbrtmode_t) returns 1.我实际上已经在这样做了不同的 struct/union 没有问题,但这是一个完全由单个字节填充的结构,所以我认为它一定与打字有关?任何关于正在发生的事情的想法都将不胜感激!

谢谢!

尝试用 #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()

那是因为您无法保证字段在结构中的对齐或填充方式。 startbrt_step 是一个 uint16_t 字段,因此在某些体系结构上,它可能需要例如 2 字节对齐。

假设 offsetof(backlightconfig_t, startbrt_default) == 22 就像您假设的那样,那么 startbrt_step 将从第 23 个字节开始,该字节未在 2 字节边界上对齐,因此插入了一个填充字节。

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

如果将所有 2 字节字段放在浮点字段之后,您可能可以解决问题,但无论如何解决方案都是脆弱的。

您可以使用某些预处理器宏强制对齐到 1 字节边界,但这不能保证在某些体系结构上工作(如果您尝试访问未对齐的内存,某些 ARM 会中止)或者它可能会增加性能损失。

您的电子表格中的错误是 enum 不简单地使用将包含其值的最窄类型。根据 GCC 手册,如果没有负值,与 enum 兼容的整数类型为 unsigned int,否则为 int

如果使用四个字节表示小的 enum 是不可接受的,请改为使用适当的整数类型声明该字段,并将 enum 用于它提供的常量。请注意您的所有常量都在该类型的范围内。

(现代)C++ 允许 enum 声明指定它们使用的基本整数类型,因此猜测更少;如果你想要一个字节大小的枚举,你可以在 C++ 中请求它。

您的 MODE DFLT 变量 #20 可能只在您看来是正确的,因为系统是小尾数法,因此 enum 的最低有效字节位于正确的偏移量处。此外,您可能使用后面两个变量 MODE RECALL 和 START BRT DFLT 的零值进行了测试,因此您没有注意到它们对 MODE DFLT 的影响。

如果您不确定偏移量,请制作一个测试程序,打印每个结构成员的偏移量和大小:

#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