memcpy 的三层策略
memcpy 的底层拷贝策略
一、memcpy 到底在做什么?
memcpy 的本质就是:
把源地址开始的
n个字节,复制到目标地址。
但真正实现时,并不一定真的一个字节一个字节地搬。
为了提高效率,实现通常会根据:
- 地址是否对齐
- 数据长度
- CPU 指令集
- 编译器优化
选择不同粒度的拷贝方式。
可以粗略理解为:
小数据 / 不对齐
↓
字节拷贝
↓
地址对齐后
↓
字 / 双字拷贝
↓
数据量很大
↓
SIMD / 专用指令注意:
这是理解
memcpy的典型优化思路,不代表所有 C 库实现都严格按照这三步执行。
二、第一层:字节拷贝
如果地址没有对齐,可以先处理少量字节:
*d++ = *s++;一次复制 1 字节。
例如:
src = 0x1003如果希望让 src 到达下一个 4 字节边界:
0x1003 → 0x1004只需要先复制 1 字节。
之后:
src = 0x1004已经 4 字节对齐。
但要注意:
源地址和目标地址必须分别考虑对齐情况。
例如:
src = 0x1003
dest = 0x2001两者的低两位不同:
src % 4 = 3
dest % 4 = 1此时不能简单认为“拷 1 字节后两边都对齐”。
实际实现会根据具体情况选择:
- 继续字节拷贝
- 使用非对齐访问
- 使用特殊的对齐策略
- 直接使用支持非对齐访问的指令
三、第二层:按更大的粒度拷贝
当地址和条件允许时,可以一次搬运多个字节。
例如 32 位 MCU:
uint32_t *d32 = (uint32_t *)dest;
const uint32_t *s32 = (const uint32_t *)src;
*d32++ = *s32++;一次逻辑上复制:
4 bytes而不是:
1 byte
1 byte
1 byte
1 byte假设剩余 98 字节:
98 / 4 = 24
98 % 4 = 2那么可以:
24 × 4 = 96 bytes最后剩下:
2 bytes再进行字节拷贝。
不过:
“一次 C 语言赋值 = 一条 CPU 指令”并不是绝对的。
编译器可能进一步优化、展开循环,甚至使用其他更高效的指令。
四、第三层:大块数据的 SIMD / 专用指令
当数据量很大时,现代 CPU 可能使用更宽的数据通路和 SIMD 指令。
例如:
普通整数指令
↓
4 / 8 bytes
SIMD
↓
16 / 32 / 64 bytes
甚至更宽不同架构使用的指令不同:
ARM
→ NEON / SVE 等
x86
→ SSE / AVX 等因此不能简单认为:
“
memcpy一定会使用 NEON/SSE。”
具体使用什么指令,取决于:
- CPU 架构
- C 库实现
- 编译器
- 优化级别
- 数据长度
- 地址对齐情况
五、为什么对齐会影响性能?
假设 CPU 更适合访问:
0x1000
0x1004
0x1008
0x100C这种 4 字节边界的数据。
如果访问:
0x1003就可能出现非对齐访问。
在某些 ARM Cortex-M 内核上,非对齐访问可能由硬件支持;在其他架构或某些指令下,可能:
- 产生额外开销
- 被拆成多次访问
- 甚至触发异常
所以高性能内存操作通常会尽量利用对齐条件。
六、为什么不能简单认为“4 字节访问就是 4 倍快”?
因为实际性能不仅取决于:
数据宽度还受到:
CPU
↓
Cache
↓
总线
↓
RAM
↓
存储器等待周期等因素影响。
例如:
一次 32 bit load并不意味着:
4 次 8 bit load一定慢 4 倍。
现代 CPU 中还可能存在:
- Cache
- 预取
- 流水线
- 指令并行
- 总线突发传输
因此:
“按字拷贝效率更高”是正确的总体理解,但不能简单用“指令数量 × 字节数”计算真实性能。
七、memcpy 的核心流程
可以把高性能 memcpy 粗略理解成:
memcpy
│
▼
判断数据规模/对齐
│
┌────────┴────────┐
▼ ▼
小数据 大数据
│ │
▼ ▼
简单拷贝 对齐/批量拷贝
│
┌─────┴─────┐
▼ ▼
字/双字 SIMD
│ │
└─────┬─────┘
▼
尾部收尾具体实现可能完全不同。
八、memcpy 和 memmove
memcpy 有一个重要限制:
源内存和目标内存不能发生重叠。
例如:
src = 0x1000
dest = 0x1004两块区域可能存在重叠。
这种情况应该使用:
memmove(dest, src, len);memmove 会根据源、目标地址的关系选择合适的复制方向,从而保证重叠情况下的数据正确性。
因此:
memcpy
→ 不允许重叠
→ 通常可以针对这个前提进行更激进的优化
memmove
→ 允许重叠
→ 必须保证复制过程中数据不会被覆盖破坏九、嵌入式中还可以使用 DMA
在 STM32 等 MCU 中,如果数据量很大,可以考虑 DMA:
CPU
│
│ 配置源地址
│ 配置目标地址
│ 配置长度
▼
DMA
│
│ 自动搬运
▼
目标内存例如:
DMA_Config dma;
dma.src_addr = (uint32_t)src_buffer;
dma.dest_addr = (uint32_t)dest_buffer;
dma.length = 1024;
DMA_Start(&dma);配置完成后,DMA 控制器可以在总线上完成数据搬运,CPU 不需要参与每一个字节的复制。
但是:
DMA 并不是“完全不占用 CPU 资源”。
CPU 仍然需要:
配置 DMA
↓
启动 DMA
↓
等待/处理中断
↓
处理完成后的数据同时 DMA 还会占用:
- 总线带宽
- SRAM/内存访问资源
因此准确地说:
DMA 可以把大量数据搬运工作从 CPU 执行单元转移给专用 DMA 控制器,但并不会让这次内存传输完全没有系统资源开销。
十、最终理解
把 memcpy 记成:
memcpy
↓
本质:复制 n 个字节
↓
实现层面根据硬件条件选择更大的拷贝粒度
↓
字节
↓
字 / 双字
↓
SIMD / 专用指令而在 STM32 中,还可以进一步:
CPU memcpy
↓
CPU自己搬运
DMA
↓
DMA控制器搬运
↓
CPU可以去执行其他任务一句话记忆:
memcpy的接口是“按字节定义的”,但底层实现为了性能,通常会尽可能利用 CPU 的宽数据访问、对齐、SIMD 等能力批量搬运;在嵌入式系统中,大块数据还可以进一步交给 DMA。

