You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

memcpy越界警告及大小端无关的SPI数据组装问题

问题背景

我尝试将结构体中的信息组装后,通过memcpy复制到SPI缓冲区,代码如下:

#include <cstdio>
#include <cstdint>
#include <cstring> /* memcpy */

using data_settings = uint8_t;

enum class channel_settings : uint8_t
{
    power_down_setting = 0,
    DA1,
    DA2,
    DA3,
    DA4,
    DA5,
    DA6,
    DA7,
    DA8,
    power_down_release,
    NA1,
    NA2,
    io_da_select,
    io_serial_parallel,
    io_parallel_serial,
    io_status_setting,
};

struct message
{
    data_settings data;
    channel_settings channel;
};

int main()
{
    message m = { 255, channel_settings::DA5 };

    uint32_t spi_buf;
    std::memcpy(&spi_buf, &m, 4);

    printf("sizeof m = %zu, spi_buf = %#010x\n", sizeof(message), spi_buf);
}

x86平台的GCC 12编译器抛出警告:

<source>:38:16: warning: 'void* memcpy(void*, const void*, size_t)' forming offset [2, 3] is out of the bounds [0, 2] of object 'm' with type 'message' [-Warray-bounds=]
   38 |     std::memcpy(&spi_buf, &m, 4);
      |     ~~~~~~~~~~~^~~~~~~~~~~~~~~~~
<source>:35:13: note: 'm' declared here
   35 |     message m = { 255, channel_settings::DA5 };
      |             ^

程序输出:

sizeof m = 2, spi_buf = 0x000005ff

当前输出显示data_settings占据低字节符合需求,但有以下疑问:

  1. 该警告的原因是什么?
  2. 是否存在结构体对齐问题?
  3. 如果是大端(big endian)系统会出现什么情况?
  4. 如何编写大小端无关的代码,确保memcpy的数据位序始终一致?

解答

1. 警告的原因

message结构体的大小仅为2字节(两个uint8_t类型成员),但调用memcpy时指定了复制4字节数据。编译器检测到代码试图从m这个仅2字节的对象中读取超出边界的2字节数据,属于缓冲区越界读取,因此触发警告。这种行为是未定义的,在不同平台可能读取到随机栈数据,甚至导致程序崩溃。

2. 结构体对齐问题

当前代码中的message结构体不存在对齐填充。因为两个成员都是1字节的uint8_t,编译器无需插入填充字节来满足对齐规则,所以sizeof(message)为2字节。只有当结构体包含更大对齐要求的成员(如uint32_t)时,才会出现对齐填充。

3. 大端系统的表现

大端系统中,多字节数据的高位字节存储在低地址处:

  • 小端系统(如x86)中,spi_buf的低字节是data(0xff),次低字节是channel(0x5),输出为0x000005ff。
  • 大端系统中,结构体低地址的data(0xff)会被放到spi_buf的最高有效字节位置,channel(0x5)放到次高字节,最终spi_buf的值会是0xff050000,完全不符合data占据低字节的需求。

4. 大小端无关的实现方案

要保证数据位序与平台无关,不能依赖结构体内存布局,需手动按指定字节序组装数据:

方案一:手动移位拼接

直接按目标字节序(如SPI常用的低位先传)拼接数据:

uint32_t spi_buf = 0;
// data在最低字节,channel在次低字节,高位补0
spi_buf |= static_cast<uint32_t>(m.data);
spi_buf |= static_cast<uint32_t>(m.channel) << 8;

方案二:显式控制字节数组顺序

先定义字节数组按目标顺序填充,再复制到spi_buf:

uint8_t buf[4] = {0};
buf[0] = m.data;          // 第0字节(最低位)存放data
buf[1] = static_cast<uint8_t>(m.channel); // 第1字节存放channel
// buf[2]、buf[3]补0
std::memcpy(&spi_buf, buf, sizeof(spi_buf));

方案三:C++20字节序转换函数

利用C++20<bit>头文件的标准函数处理字节序:

#include <bit>

uint32_t raw_value = static_cast<uint32_t>(m.data) | (static_cast<uint32_t>(m.channel) << 8);
// 转换为小端字节序(SPI常用)
if constexpr (std::endian::native == std::endian::big) {
    spi_buf = std::byteswap(raw_value);
} else {
    spi_buf = raw_value;
}

以上方法均不依赖结构体内存布局,能保证在任何大小端平台上,spi_buf的字节序符合需求。


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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.29 05:47:54