C语言入门,内存函数知识讲解

前言

在 C 语言中,字符串操作有 strcpy、strncpy、strcmp 等函数,但它们都只针对字符串,遇到整型数组、结构体数组这类数据就无能为力了。而实际开发中,我们经常需要按字节对任意类型的内存块进行拷贝、设置和比较,这时就要用到内存操作函数。

本文围绕 <string.h> 中四个常用的内存函数展开:memcpy、memmove、memset、memcmp。每个函数都会从函数原型、功能、参数、返回值讲起,再配合示例代码和调试截图演示实际效果,最后尝试手写模拟实现,帮助大家真正理解它们的工作原理,尤其是 memcpy 与 memmove 在处理内存重叠时的关键差异。

阅读本文需要具备指针和数组的基础知识,所有示例均在 VS2022 环境下验证。

目录

1.memcpy函数

我们在之前已经学过了strcpy和strncpy函数,它们是只用于拷贝字符串的,是有局限性的。假如我们要拷贝一个整型数组呢?结构体数组呢?你也许会想到通过下标循环去实现,但是我们其实有可以一次性完成拷贝的方法,所以这时候我们就要去学习一个新函数,它叫做memcpy函数,先来了解一下函数的相关知识吧!

memcpy函数的原型:

c 复制代码
   void* memcpy(void* destination,void* source,size_t num);

功能:

· memcpy是完成内存块拷贝的,不在乎内存里存放的数据

· 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置

· 如果source和destination存在重叠部分,复制的结果都是未定义的,此时使用memcpy函数是实现不了的,要使用到接下来学习的memmove函数

· memcpy使用时要包含头文件:<string.h>

参数:

destination:指针,指向目标空间,拷贝的数据存放在这里

source:指针,指向源空间,要拷贝的数据从这里来

num:要拷贝的数据占据的字节数

返回值:

拷贝完成后,返回目标空间的起始地址

了解完这些知识后,我来尝试简单地使用一下这个函数:

调试一下,看一下arr2数组的变化:

再来拷贝一个float类型的例子:

调试一下,看一下arr2数组的变化:

由于小数是以2进制的形式存放的,所以可能不能精确的存放,但是这里关注的是数据能否拷贝成功,很显然数据能够通过内存块拷贝过去。

在简单使用之后,我们可不可以来模拟实现一下这个函数呢?其实是可以的

先对怎么模拟函数进行一个思路分析,以上面整型数组为例:

从第一个字节开始,往后拷贝到num个字节

有了以上的思路分析之后,我们就可以尝试来写出代码:

这里我们成功模拟实现了memcpy函数,但是这是我们对两块独立的空间进行内存块拷贝的,如果我们就对一块空间进行拷贝,还能得到我们想要的结果吗?

来看下面一段代码:结合我们上面模拟实现的my_memcpy函数

我们期望的结果是1、2、1、2、3、4、5、8、9、10,来看运行结果:

但是运行结果怎么跟我们期望的结果不一样啊?

原因:1拷贝到3处,3被覆盖为1;2拷贝到4处,4被覆盖为2;3处此时是1,5被覆盖为1;4处此时是2,6被覆盖为2;5处此时是1,7被覆盖为1,就呈现出这样的结果。

说明了memcpy函数只能处理不重叠的数据,处理不了重叠的数据的,在前面介绍函数的功能的时候,我们提到memmove函数是可以处理不重叠和重叠的数据的。

但是在我们使用的VS2022中,使用memcpy函数是可以处理重叠的数据的,如下:

运行结果:

我们要求memcpy函数可以处理不重叠的数据就行,但是在VS2022中,这个函数的实现超额完成了memmove的任务,但这只是在这个编译器下出现的情况,顺带给大家提一下,做个了解即可。

2.memmove函数

上面我们提到memmove函数可以处理重叠的数据,在这里,我们就要对memmove函数进行学习。

memmove函数的原型:

c 复制代码
   void* memmove(void* destination,const void* source,size_t num);

功能:

· memmove函数也是完成内存块拷贝的

· 与memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

· memmove的使用需要包含<string.h>

参数:

destination:指针,指向目标空间,拷贝的数据存放在这里

source:指针 ,指向源空间,要拷贝的数据从这里来

num:要拷贝的数据占据的字节数

返回值:

拷贝完成后,返回目标空间的起始地址

你也许会发现,参数和返回值居然跟memcpy函数一样,在了解完这些知识后,我来尝试简单地使用一下这个函数:

运行结果:

再来一个用memmove处理源空间和目标空间内存重叠的数据的例子:

运行结果:

在简单使用之后,我们可不可以来模拟实现一下这个函数呢?其实是可以的

先对怎么模拟函数进行一个思路分析,以上面整型数组为例:

我们把这两种情况结合起来看的话,最后是可以确定拷贝的方式是由两种的:

我更推荐第2种方式,有了以上的思路分析之后,我们对第2种思路来进行实现:

3.memset函数

memset函数的原型:

c 复制代码
   void* memset(void* ptr,int value,size_t num);

功能:

· memset函数是用来设置内存块的内容的,将内存中指定长度的空间设置为特定的内容。

· memset的使用需要包含<string.h>

参数:

ptr:指针,指向要设置的内存空间,也就是存放了要设置的内存空间的起始地址

value:要设置的值,函数将会把value值转换为unsigned char的数据进行设置的,也就是以字节为单位来设置内存块的。

num:要设置的内存长度,单位是字节。

**返回值:**返回的是要设置的内存空间的起始地址

在了解完这些知识后,我来尝试简单地使用一下这个函数:

字符串可以被字符设置,那么对于整型是否能完成设置呢?

调试一下:

我们可以观察到memset是将每个字节都设置成1,针对的是字节;而一个整型占4个字节,要设置元素的话,一次是要设置4个字节的,显然就不合适。

但是将每个元素都设置为0的话,这个倒是可以实现,memset设置一个字节为0,4个字节加起来还是0,就可以是实现设置为0的要求了。

总结:

当有一块内存空间需要设置内容时,可以使用memset函数,它是以字节为单位进行设置的

4.memcmp函数

memcmp函数的原型:

c 复制代码
   int memcmp(const void* prt1,const void* ptr2,size_t num);

功能:

· 比较指定的两块内存块的内容,比较从prt1和ptr2指针指向的位置开始,向后一对一对字节进行比较

· memcmp的使用需要包含<string.h>

参数:

prt1:指针,指向一块待比较的内存块

ptr2:指针,指向另一块待比较的内存块

num:指定的比较长度,单位是字节

返回值:

若第一个内存块的字节的内容大于第二块内存块的字节的内容,返回大于0的数;小于返回小于0的数;若比较完所有的字节的内容都相等,则返回0

在了解完这些知识后,我来尝试简单地使用一下这个函数:

调试一下:

前16个字节是一样的,所以得到==,那前17个字节比较呢?

前16个字节一样,比较第17个字节,5小于8,所以输出<

总结:

如果要比较2块内存单元的数据大小,可以使用memcmp函数,指定比较长度,返回值反映大小关系。

5.总结

  • memcpy 与 memmove 的区别

    • memcpy 只负责内存块拷贝,要求源空间与目标空间不重叠;一旦重叠,复制结果是未定义的。
    • memmove 同样完成内存块拷贝,但允许源空间与目标空间重叠,内部会根据重叠方向选择从前向后或从后向前拷贝。
    • 两者参数与返回值完全一致:(destination, source, num),返回目标空间的起始地址,使用时都需要包含 <string.h>。
    • 在 VS2022 等部分编译器下,memcpy 的实现也能正确处理重叠数据,但这属于实现层面的「超额完成」,不能作为可移植的保证。
  • 使用注意事项

    • 拷贝长度 num 的单位是字节 ,不是元素个数,拷贝数组时要写成 n * sizeof(元素类型)。
    • 拷贝前要确保目标空间足够大,避免越界写入。
    • 源空间与目标空间存在重叠时,一律使用 memmove,不要依赖 memcpy。
    • memset 以字节为单位设置,适合把内存清零或按字节填充;给 int 数组设置非 0 值(如 1)会得到意料之外的结果。
    • memcmp 按字节逐对比较,返回大于 0、小于 0 或 0,适合比较任意类型的内存块,但比较结构体时要留意填充字节的影响。
  • 模拟实现思路

    • my_memcpy:把 destination、source 强转为 char*,用一个循环按字节从前往后逐个拷贝 num 次,最后返回目标空间的起始地址。
    • my_memmove:先判断源空间与目标空间的相对位置------若 destination < source 或两区间不重叠,则从前往后拷贝;若 destination > source 且区间重叠,则从后往前拷贝,从而避免数据被提前覆盖。
    • 两种实现都要保留 void* 的通用性,并在内部转换为 char* 做字节级操作。
相关推荐
Zwawa4 小时前
C语言函数指针与宏实战:从回调、表驱动菜单到X-Macro,讲透嵌入式代码解耦
c语言·单片机·嵌入式
David猪大卫6 小时前
【C++修炼】map 和 set 的使用
c语言·开发语言·数据结构·c++·经验分享·笔记·学习
HPtiger6 小时前
C++ 入门笔记:从 C 到 C++ 的核心过渡(命名空间、输入输出、缺省参数、重载、引用、内联)
c语言·c++·学习
fengci.7 小时前
CTF2MISC中等部分
开发语言·前端·学习
xys_6789 小时前
JavaEE通关手册:初识Java
java·开发语言
安妮细水长流10 小时前
ad7124
c语言·单片机
ShineWinsu10 小时前
对于Qt:QTableWidget常见属性以及QTableWidgetItem类的解析
开发语言·c++·qt·gui·客户端·表格·qtablewidget
ShineWinsu11 小时前
对于Qt:QListWidget常见属性以及QListWidgetItem类的解析
开发语言·c++·qt·客户端·列表·qlistwidget·qlistwidgetitem
吴名氏.11 小时前
Java转AI Agent教程(第十二周)检索与切分
java·开发语言·人工智能·ai·ai agent