首页 > 行业资讯 > 正文

【C语言进阶】使用memcpy你需要注意的一个问题

日常编程中,memcpy可以算得上是使用频次非常高的函数,那么有些小点可能你没有关注到,本文将给你提个小醒。

1 写在前面

作为一个C语言程序员,标准库函数使用频次排行榜上,memcpy在上面的排行一定会非常靠前,就算排不了第一,肯定也排得上前三!

这个函数的使用虽然简单,但是在没有深入理解这个函数的时候,往往容易出问题。

本文将以一个实际的案例展开,带你全面了解这个函数。

2 问题现场

问题现场是这样的,但是我正在调试stm32的一个DMA驱动代码,在DMA代码配置中使用的half-word模式,即半字,也就是双字节模式,所以我就定义个一个uint16_t的buffer,如下:

复制​ #define DMA_SIZE 1024uint16_t g_dma_buffer[DMA_SIZE]; ​

整体的实现思路就是,当需要DMA去搬运数据的时候,打卡DMA,等到DMA搬运完数据之后,触发一个信号量,然后应用层就过来拷贝数据。

于是我开了这样的一个接口以供应用层来拷贝数据。

复制​ void copy_data_from_dma_buffer(uint8_t *data_out, uint16_t buffer_size) { //call memcpy … memcpy(data_out, (uint8_t *)g_dma_buffer, DMA_SIZE); } ​ // 应用层调用的代码如下uint8_t g_app_buffer[4096]; ​ copy_data_from_dma_buffer(g_app_buffer, sizeof(g_app_buffer)); ​

那么问题来了,我那个memcpy使用正确了吗?

凭着多年使用memcpy的感觉,好像发现不对劲;

但是被调试代码的过程蒙晕了头脑,一时半会又想不起哪有问题!

3 知识点补充

趁着这个机会,我好好地补充学习了关于memcpy的前前后后,重新梳理了一些它的知识点。

3.1 标准库对memcpy的描述

这个描述,我们可直接看Linux下面的man命令:

复制​ NAME memcpy – copy memory area ​ SYNOPSIS #includevoid *memcpy(void *dest, const void *src, size_t n); ​ DESCRIPTION The memcpy() function copies n bytes from memory area src to memory area dest. The memory areas must not overlap. Use memmove(3) if the memory areas do overlap. ​ RETURN VALUE The memcpy() function returns a pointer to dest. ​

请注意它的【描述】部分,这里说到它的第三个参数n,指的是【字节】数,而不是半字,也不是字;也就是它的所有拷贝计数都是按字节数来算的。

同时,还需要留意它的返回值,表示的是dest的原地址。

3.2 一个简易版本的源码实现

根据memcpy的描述,我们可以实现一个简易版本的memcpy,示例如下:

复制​ void *my_memcpy(void *dest, const void *src, size_t n) { assert(dest && src && (n > 0)); if (dest == src) { ; } else { unsigned char *p_dest = (unsigned char *)dest; unsigned char *p_src = (unsigned char *)src; size_t i; for (i = 0; i < n; i++) { *p_dest++ = *p_src++; } } ​ return dest; } ​

感兴趣的朋友,可以写写测试用例,测试测试该接口。

3.3 问题答疑

回到上面的问题点,毫无疑问,我之前的使用方法是错误的。

原因在于我在定义 DMA_SIZE 时,其实这个是双字节的计算,如果转换成单字节计数,还需要乘以个2。

也就是那段调用代码需要修改为:

复制​ //call memcpy … memcpy(data_out, (uint8_t *)g_dma_buffer, 2 * DMA_SIZE); ​

那么有没有一个不考虑使用乘2的写法呢?自然是有的,我们可以借助sizeof来帮助我们求g_dma_buffer的字节数,修改如下:

复制​ //call memcpy … memcpy(data_out, (uint8_t *)g_dma_buffer, sizeof(g_dma_buffer)); ​

至此,问题完美解决,驱动代码也没出问题了。

4 小小总结

再次总结下几个小点吧。

memcpy的第三个参数n,指的是字节数,如果传入的src buffer不是单字节空间时,需要注意n的取值问题; memcpy函数的返回值表示的dest的原地址,并不是拷贝偏移之后的地址; memcpy本身不是安全操作,拷贝的过程中,需要调用者来保证数据拷贝不会溢出,这点也特别需要注意。 5 更多分享

[架构师李肯]

架构师李肯全网同名 ),一个专注于嵌入式IoT领域的架构师。有着近10年的嵌入式一线开发经验,深耕IoT领域多年,熟知IoT领域的业务发展,深度掌握IoT领域的相关技术栈,包括但不限于主流RTOS内核的实现及其移植、硬件驱动移植开发、网络通讯协议开发、编译构建原理及其实现、底层汇编及编译原理、编译优化及代码重构、主流IoT云平台的对接、嵌入式IoT系统的架构设计等等。拥有多项IoT领域的发明专利,热衷于技术分享,有多年撰写技术博客的经验积累,连续多月获得RT-Thread官方技术社区原创技术博文优秀奖,荣获[CSDN博客专家]、[CSDN物联网领域优质创作者]、[2021年度CSDN&RT-Thread技术社区之星]、[2022年RT-Thread全球技术大会讲师]、[RT-Thread官方嵌入式开源社区认证专家]、[RT-Thread 2021年度论坛之星TOP4]、[华为云云享专家(嵌入式物联网架构设计师)]等荣誉。坚信【知识改变命运,技术改变世界】!

审核编辑:汤梓红

猜你喜欢