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占据低字节符合需求,但有以下疑问:
- 该警告的原因是什么?
- 是否存在结构体对齐问题?
- 如果是大端(big endian)系统会出现什么情况?
- 如何编写大小端无关的代码,确保
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
相关产品推荐
相关产品推荐

