You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

ARM Cortex-M4(STM32L476RG)结构体去填充实现68字节需求

解决STM32 Cortex-M4结构体填充问题的两种方案

我来帮你搞定这个结构体填充导致的二进制写入问题,先拆解一下问题根源,再给两种可行的解决办法。

为什么会出现4字节填充?

你的结构体里,PacketNumberunsigned long(4字节),它后面跟着的是int64_t y(8字节)。Cortex-M4的编译器为了保证数据对齐(提升访问性能,避免某些情况下的总线错误),会要求8字节类型(比如int64_t)的起始地址是8的倍数。

看一下地址:PacketNumber从48开始,占4字节到51结束,下一个地址是52,52不是8的倍数(52 ÷ 8 = 6.5),所以编译器会自动插入4字节填充,把y的起始地址推到56(8×7),这就导致结构体总大小多了4字节,从68变成72。

方法1:用编译器指令强制消除结构体填充

你可以给结构体加上编译器的“紧凑打包”属性,让编译器不插入填充字节。注意不同编译器的语法略有区别:

GCC编译器(比如STM32CubeIDE默认用的GCC)

修改结构体定义,加上__attribute__((packed))

struct data {
    double Latitude;        // 00-07
    double Longtitude;      // 08-15
    float HorizontalAcc;    // 16-19
    float Altitude;         // 20-23
    float AltitudeAcc;      // 24-27
    float SpeedInMS;        // 28-31
    float SpeedAcc;         // 32-35
    float Heading;          // 36-39
    float HeadingAcc;       // 40-43
    int Data;               // 44-47
    unsigned long PacketNumber; // 48-51
    int64_t y;              // 52-59(无填充,直接跟着PacketNumber)
    int64_t x;              // 60-67
} __attribute__((packed));

ARMCC编译器(比如Keil MDK)

__packed关键字:

__packed struct data {
    double Latitude;        // 00-07
    double Longtitude;      // 08-15
    float HorizontalAcc;    // 16-19
    float Altitude;         // 20-23
    float AltitudeAcc;      // 24-27
    float SpeedInMS;        // 28-31
    float SpeedAcc;         // 32-35
    float Heading;          // 36-39
    float HeadingAcc;       // 40-43
    int Data;               // 44-47
    unsigned long PacketNumber; // 48-51
    int64_t y;              // 52-59
    int64_t x;              // 60-67
};

⚠️ 注意事项:

  • 打包后的结构体可能会导致非对齐访问,Cortex-M4虽然支持非对齐访问,但会降低性能,极端情况下(比如某些外设寄存器访问)可能出问题,但你的场景是写入文件,影响不大。
  • 不同编译器的打包属性可能不兼容,如果跨编译器移植要注意。

方法2:手动逐个写入结构体成员(更安全,无对齐风险)

如果不想修改结构体的对齐方式,你可以直接逐个写入每个成员,跳过编译器自动插入的填充字节。这样不管结构体有没有填充,都能准确写入68字节的数据:

#include <stdio.h>

void write_data_to_file(struct data *d, const char *filename) {
    FILE *fp = fopen(filename, "wb");
    if (!fp) {
        // 处理文件打开错误
        return;
    }

    // 逐个写入每个成员
    fwrite(&d->Latitude, sizeof(d->Latitude), 1, fp);
    fwrite(&d->Longtitude, sizeof(d->Longtitude), 1, fp);
    fwrite(&d->HorizontalAcc, sizeof(d->HorizontalAcc), 1, fp);
    fwrite(&d->Altitude, sizeof(d->Altitude), 1, fp);
    fwrite(&d->AltitudeAcc, sizeof(d->AltitudeAcc), 1, fp);
    fwrite(&d->SpeedInMS, sizeof(d->SpeedInMS), 1, fp);
    fwrite(&d->SpeedAcc, sizeof(d->SpeedAcc), 1, fp);
    fwrite(&d->Heading, sizeof(d->Heading), 1, fp);
    fwrite(&d->HeadingAcc, sizeof(d->HeadingAcc), 1, fp);
    fwrite(&d->Data, sizeof(d->Data), 1, fp);
    fwrite(&d->PacketNumber, sizeof(d->PacketNumber), 1, fp);
    fwrite(&d->y, sizeof(d->y), 1, fp);
    fwrite(&d->x, sizeof(d->x), 1, fp);

    fclose(fp);
}

这种方法的好处是:

  • 完全避免了结构体填充的影响,写入的字节数严格等于所有成员的总和。
  • 不会破坏结构体的对齐特性,结构体在内存中的访问依然是高效的。
  • 跨编译器兼容性更好,不用依赖编译器的扩展属性。

总结

如果你的场景只需要写入文件,且不担心非对齐访问的性能问题,方法1更简单;如果想要更安全、更通用的方案,方法2是更好的选择。

内容的提问来源于stack exchange,提问作者Noob14

火山引擎 最新活动