嵌入式开发-memcpy与memmove 技术详解

一、内存拷贝基础概念

内存拷贝是嵌入式开发中最基础、最高频的数据操作,核心作用是将一段连续内存空间的数据,完整原样搬运到另一段内存空间中。广泛应用于数组数据复制、结构体赋值、缓冲区数据搬运、外设数据缓存、数据移位等场景。

嵌入式开发中内存拷贝的核心判定标准为内存地址是否重叠,这也是区分 memcpy 和 memmove 两个核心函数的关键依据,合理选择函数可有效避免数据错乱、内存踩踏、程序死机等问题。

二、memcpy 函数详解

2.1 函数原型

复制代码

void *memcpy(void *dest, const void *src, size_t n);

2.2 参数与返回值说明

  • dest:目标内存地址,数据拷贝的存放位置

  • src:源内存地址,待拷贝的数据源,添加 const 修饰,保证源数据只读不可修改

  • n:需要拷贝的字节数量

  • 返回值:目标内存地址的指针,支持链式调用

2.3 核心特性

  • 无内存重叠处理逻辑,仅支持无重叠内存拷贝,内存重叠时拷贝结果不可控,极易出现数据错乱

  • 以字节为单位逐字节拷贝,不识别 int、char、结构体等数据类型,通用性强

  • 内部无地址判断、容错校验逻辑,执行开销小、拷贝速度快

2.4 适用场景

仅适用于源内存与目标内存完全独立、无任何重叠的场景,是无重叠场景下的最优选择。

2.5 代码示例

复制代码

#include <string.h> #include <stdio.h> int main(void) { char buf1[10] = "123456"; char buf2[10] = {0}; // 无重叠内存拷贝,拷贝6个字节有效数据 memcpy(buf2, buf1, 6); printf("拷贝结果:%s\r\n", buf2); return 0; }

三、memmove 函数详解

3.1 函数原型

复制代码

void *memmove(void *dest, const void *src, size_t n);

函数参数、返回值类型与 memcpy 完全一致,调用方式无区别。

3.2 核心特性

  • 支持内存重叠与非重叠全场景拷贝,无论内存是否重叠,均可保证数据拷贝准确无误,安全性极高

  • 内部内置内存地址判断逻辑,自动适配正向、反向两种拷贝方式

  • 相较于 memcpy 多了地址判断逻辑,执行开销略大,拷贝速度稍慢

3.3 重叠内存拷贝逻辑

memmove 会通过对比目标地址与源地址的高低,自动切换拷贝方式,彻底规避数据覆盖问题:

  • 目标地址 < 源地址:采用正向顺序拷贝,从内存低地址向高地址逐字节搬运

  • 目标地址 > 源地址:采用反向倒序拷贝,从内存末尾向起始地址搬运,避免未拷贝数据被提前覆盖

3.4 适用场景

适用于内存存在重叠、不确定内存是否重叠的场景,常见于数组内部数据移位、缓冲区原地数据搬运等操作。

3.5 内存重叠拷贝示例

复制代码

#include <string.h> #include <stdio.h> int main(void) { char arr[10] = "abcdef"; // 自身内存重叠拷贝:将前3个字节数据拷贝到数组下标2的位置 memmove(arr + 2, arr, 3); printf("重叠拷贝结果:%s\r\n", arr); // 输出结果:ababcdef return 0; }

四、memcpy 与 memmove 核心对比

对比特性 memcpy memmove
内存重叠兼容性 不支持,重叠拷贝结果错乱 完全支持,全场景拷贝安全
执行效率 高,无多余判断开销 略低,需做地址逻辑判断
内部实现逻辑 单纯正向逐字节拷贝 地址判定 + 正向/反向双向拷贝
适用范围 仅限无重叠内存场景 所有内存拷贝场景,通用性强

五、嵌入式开发高频易错点

5.1 内存重叠误用

这是嵌入式开发最常见的问题。当源内存与目标内存存在重叠时,使用 memcpy 会覆盖未拷贝的原始数据,导致数据错乱、功能异常,严重时引发系统崩溃。内存不确定是否重叠时,优先使用 memmove。

5.2 拷贝长度越界

拷贝字节数 n 不得超过源内存、目标内存的实际申请空间。越界拷贝会造成内存踩踏,篡改其他变量、堆栈数据,引发程序跑飞、死机、硬件异常等问题。

5.3 缺失空指针校验

嵌入式裸机、RTOS 开发中,无系统内存保护机制。若 src 或 dest 为空指针,直接调用拷贝函数会触发硬件异常、程序卡死,调用前必须做空指针判断。

5.4 结构体直接拷贝误区

两个完全相同类型的结构体,可直接通过内存拷贝完成整体赋值,效率高于逐成员赋值。但需注意:含指针成员的结构体不建议直接整体拷贝,会出现浅拷贝问题,导致数据异常。

复制代码

// 常规结构体安全拷贝示例 typedef struct { int age; char name[10]; } Stu_t; Stu_t stu_a = {18, "Zhang"}; Stu_t stu_b = {0}; // 整体结构体拷贝 memcpy(&stu_b, &stu_a, sizeof(Stu_t));

5.5 非法内存拷贝

禁止拷贝数组、变量申请空间以外的非法内存,该部分内存数据随机且不受管控,极易引发未知异常。

六、底层手写实现(助理解原理)

6.1 简易版 memcpy 实现

纯正向逐字节拷贝,无重叠处理,还原原生 memcpy 核心逻辑

复制代码

void my_memcpy(void *dst, const void *src, size_t len) { // 强制转为字节指针,按字节拷贝 char *d = (char*)dst; const char *s = (const char*)src; while(len--) { *d++ = *s++; } }

6.2 安全版 memmove 实现

增加地址判断,兼容重叠与非重叠所有场景,还原原生 memmove 安全逻辑

复制代码

void my_memmove(void *dst, const void *src, size_t len) { char *d = (char*)dst; const char *s = (const char*)src; // 目标地址在源地址前:正向拷贝 if(d < s) { while(len--) { *d++ = *s++; } } // 目标地址在源地址后:反向拷贝,避免数据覆盖 else { while(len--) { *(d + len) = *(s + len); } } }

七、嵌入式同类内存操作API 拓展详解

在嵌入式 C 开发中,除了 memcpy、memmove,还有 memset、memcmp、memccpy、strcpy/strncpy 等高频内存/数据拷贝 API,均为底层数据操作核心函数。很多新手容易混淆其适用场景,本节统一对比讲解,适配嵌入式开发规范。

7.1 memset 内存初始化函数

函数原型

复制代码

void *memset(void *s, int c, size_t n);

核心功能

将指定内存区域的每个字节,统一赋值为参数 c(仅低8位生效),多用于内存清零、缓冲区初始化。

嵌入式特性与易错点

  • 按字节赋值,只能清零/置0xFF,无法直接给 int 类型数组赋值 1、2 等非0值;

  • 嵌入式缓冲区、结构体、数组初始化必备函数,杜绝脏数据。

示例代码

复制代码

char buf[32]; memset(buf, 0x00, sizeof(buf)); // 缓冲区整体清零

7.2 memcmp 内存比较函数

函数原型

复制代码

int memcmp(const void *s1, const void *s2, size_t n);

核心功能

逐字节对比两段内存数据,不区分数据类型,常用于校验拷贝结果、校验外设接收数据。

返回值规则

  • 返回 0:前 n 字节内存数据完全一致;

  • 返回非0:存在数据差异(第一段大于/小于第二段)。

7.3 memccpy 安全截断拷贝函数

函数原型

复制代码

void *memccpy(void *dest, const void *src, int c, size_t n);

核心功能

进阶版内存拷贝,在拷贝 n 字节的同时,检测是否出现指定字符 c:遇到 c 则立即停止拷贝,自动截断,适合带结束标识的缓冲区拷贝。

适用场景

串口、CAN 等不定长报文拷贝,自动识别报文结束符,避免多余拷贝。

7.4 strcpy / strncpy 字符串专属拷贝函数

核心区别(与memcpy)

memcpy 是纯内存二进制拷贝 ,不识别结束符;str 系列是字符串拷贝 ,依赖 \0 结束符,仅适用于字符串。

1、strcpy

自动识别 \0 结束拷贝,无长度限制 ,极易造成内存越界,嵌入式工程禁止使用

2、strncpy

限定最大拷贝长度 n,规避越界风险,但不会自动补 \0,使用后需手动补结束符,避免字符串乱码。

八、所有内存拷贝 API 终极对比(嵌入式专用)

|---------|--------|-------|---------|----------------|
| API函数 | 是否支持重叠 | 是否按字节 | 是否识别结束符 | 嵌入式推荐场景 |
| memcpy | 不支持 | 是 | 否 | 无重叠、追求高效率二进制拷贝 |
| memmove | 支持 | 是 | 否 | 不确定重叠、缓冲区原地移位 |
| memset | --- | 是 | 否 | 内存清零、缓冲区初始化 |
| memcmp | --- | 是 | 否 | 内存数据校验、报文比对 |
| memccpy | 不支持 | 是 | 是 | 不定长报文、带结束符数据拷贝 |
| strncpy | 不支持 | 是 | 是 | 专用字符串安全拷贝 |

九、嵌入式开发使用总结

  1. 明确无内存重叠:优先使用 memcpy,最大化拷贝效率,适配常规数据复制场景;

  2. 内存未知/存在重叠:统一使用 memmove,牺牲微小效率换取程序稳定性,规避数据异常;

  3. 裸机开发必须添加空指针校验、长度边界校验,杜绝硬件异常与内存踩踏;

  4. 数组、缓冲区、普通结构体批量数据搬运,优先使用标准内存拷贝函数,代码简洁且执行效率高;

  5. 二进制数据一律用 mem 系列函数,禁止使用 str 系列,避免截断、乱码问题;

  6. 初始化内存强制使用 memset,字符串安全拷贝统一使用 strncpy,废弃 strcpy。

  7. 明确无内存重叠:优先使用 memcpy,最大化拷贝效率,适配常规数据复制场景;

  8. 内存未知/存在重叠:统一使用 memmove,牺牲微小效率换取程序稳定性,规避数据异常;

  9. 裸机开发必须添加空指针校验、长度边界校验,杜绝硬件异常与内存踩踏;

  10. 数组、缓冲区、普通结构体批量数据搬运,优先使用标准内存拷贝函数,代码简洁且执行效率高。

相关推荐
_Emma_10 小时前
【Linux网络】Linux网络协议栈问题汇集
linux·网络·网络协议
minji...10 小时前
Linux 网络基础之数据链路层(十三)认识以太网,认识MAC地址和MTU,局域网(以太网)通信原理
linux·网络·以太网·交换机·数据链路层·mac地址·局域网通信
之歆10 小时前
Day17_JavaScript高级核心垃圾回收执行上下文闭包完全指南(上)
开发语言·javascript·ecmascript
minji...11 小时前
Linux 网络基础之数据链路层(十四)ARP协议及原理,ARP欺骗
linux·网络·智能路由器·ip·arp协议·arp欺骗
xu_wenming11 小时前
zephyr从会用走向精通
c语言·嵌入式硬件·物联网
weixin_5500831511 小时前
基于Python的豆瓣电影数据爬取与可视化分析
开发语言·python
Hwang25211 小时前
Spring 框架 -01 -单例池的一些理解
java
小猫咪0111 小时前
Linux 文件权限详解:chmod、chown、umask 到底怎么用?
linux
xyq202411 小时前
jQuery Mobile Data 属性详解
开发语言
fengfuyao98511 小时前
STM32 控制 SG90 舵机指南
stm32·单片机·嵌入式硬件