当前位置: 首页 > news >正文

【C语言内存函数完全指南】:memcpy、memmove、memset、memcmp 的用法、区别与模拟实现(含代码示例)

在这里插入图片描述

✨ 用 清晰易懂的图解 帮你建立直观认知 ,用通俗的 代码语言 帮你落地理解, 让每个知识点都能 轻松get
🚀 个人主页 :0xCode小新 · CSDN
🌱 代码仓库 :0xCode小新· Gitee
📌 专栏系列

  • 📖 《c语言》
    📖 《鸿蒙应用开发项目教程》

💬 座右铭“ 积跬步,以致千里。”

在这里插入图片描述

在C语言编程中,对内存的直接操作是每个开发者必须掌握的核心技能。无论是数据复制、移动、初始化还是比较,都离不开高效且安全的内存函数。本篇内容将以清晰易懂的方式,带你深入理解C语言中四大内存函数——memcpymemmovememsetmemcmp 的使用方法、适用场景,并手把手教你如何模拟实现它们。这篇指南将为你提供实用的知识与代码示例,助你在内存管理中游刃有余。

文章目录

  • 1. [memcpy](https://legacy.cplusplus.com/reference/cstring/memcpy/?kw=memcpy)使用和模拟实现
        • 1. 函数是什么?
        • 2. 核心特性与使用场景
        • 3. 实战示例
        • 4. 动手模拟实现
        • 5. 重要提醒:什么时候不能用 memcpy?
  • 2. [memmove](https://legacy.cplusplus.com/reference/cstring/memmove/)使用和模拟实现
        • 1. 函数是什么?为什么需要它?
        • 2. 核心问题:什么是内存重叠?为什么 memcpy 处理不了?
        • 3. memmove 的智能解决方案
        • 4. 动手模拟实现
        • 5. 验证实现
        • 6. 使用建议
  • 3. [memset](https://legacy.cplusplus.com/reference/cstring/memset/)函数的使用和模拟实现
        • 1. 函数是什么?
        • 2. 核心特性与工作原理
        • 3. 实战示例
        • 4. 重要注意事项
        • 5. 动手模拟实现
        • 6. 验证实现
  • 4. [memcmp](https://legacy.cplusplus.com/reference/cstring/memcmp/)函数的使用和模拟实现
        • 1. 函数是什么?
        • 2. 核心特性与工作原理
        • 3. 返回值规则详解
        • 4. 实战示例
        • 5. 与 strcmp 的区别
        • 6. 动手模拟实现
        • 7. 验证实现
  • 结语

1. memcpy使用和模拟实现

1. 函数是什么?

memcpy 是 C 语言标准库中一个用于内存拷贝的函数。它的核心任务是将一块内存中的数据,原封不动地复制到另一块内存中。

函数原型

void *memcpy(void *destination, const void *source, size_t num);
  • destination: 指向目标内存区域的指针,复制的内容将存放于此。
  • source: 指向源内存区域的指针,复制的内容来源于此。
  • num: 要复制的字节数
  • 返回值: 返回指向 destination 的指针。

在这里插入图片描述

2. 核心特性与使用场景
  • 按字节复制memcpy 不关心内存中存储的是什么数据类型(int, char, struct 等),它只负责忠实地、一个字节一个字节地进行复制。
  • 不关心终止符:与 strcpy 不同,memcpy 遇到 '\0' 并不会停止。它只认准你传入的 num 参数,复制完指定字节数后才会停下。这使得它可以用于复制任何二进制数据,如图片、结构体等。
  • 不处理重叠:这是 memcpy 最关键的一个限制。如果 sourcedestination 所指向的内存区域有重叠,使用 memcpy 的结果是“未定义的”。这意味着可能会复制出错,程序崩溃,或者出现各种意想不到的情况。处理重叠内存是 memmove 的任务。
3. 实战示例

让我们通过一个代码示例来看看它的实际效果:

#include <stdio.h>
#include <string.h> // 包含 memcpy 的头文件int main() {int arr1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};int arr2[10] = {0}; // 目标数组,初始化为0// 将 arr1 的前 20 个字节(即前5个int元素)复制到 arr2memcpy(arr2, arr1, 20);// 打印 arr2 的结果for (int i = 0; i < 10; i++) {printf("%d ", arr2[i]);}// 输出:1 2 3 4 5 0 0 0 0 0return 0;
}

代码解读:

  • memcpy(arr2, arr1, 20); 这行代码的意思是:从 arr1 的首地址开始,拷贝 20 个字节的数据到 arr2
  • 在我们的系统中,一个 int 类型通常占 4 个字节。因此,20 个字节正好对应 5 个 int 元素。
  • 结果就是 arr2 的前 5 个元素变成了 1, 2, 3, 4, 5,后面的元素保持为 0。
4. 动手模拟实现

理解一个函数最好的方式就是亲手实现它。下面我们来看看如何模拟实现一个自己的 memcpy

#include <assert.h>void* my_memcpy(void* dst, const void* src, size_t count) {// 1. 保存目标指针的起始位置,用于最后返回void* ret = dst;// 2. 安全检查:确保源指针和目标指针不是空指针assert(dst && src);// 3. 逐字节拷贝while (count--) {// 将 void* 强制转换为 char*,因为char类型占1个字节,便于逐字节操作*(char*)dst = *(char*)src;// 移动指针到下一个字节dst = (char*)dst + 1;src = (char*)src + 1;}// 4. 返回目标内存的起始地址return ret;
}

实现要点解析:

  1. void* 指针的妙用void* 是“无类型指针”,可以接受任何类型的地址。这赋予了 memcpy 处理任意数据类型的能力。
  2. 类型转换:在函数内部,我们将 void* 转换为 char*。因为 char 类型的大小是 1 字节,这样 (char*)dst + 1 就正好移动一个字节,实现了逐字节拷贝。
  3. 断言 assert:这是一种防御性编程技巧,确保传入的指针是有效的,避免对空指针进行操作导致程序崩溃。
  4. 返回值:返回最初的 dst 指针,是为了支持函数的链式调用,例如 printf(“%s”, (char*)memcpy(dest, src, n))。
5. 重要提醒:什么时候不能用 memcpy?

当源内存和目标内存发生重叠时!

请看这个反面例子:

int arr[] = {1, 2, 3, 4, 5};
// 将前3个元素复制到从第2个元素开始的位置
my_memcpy(arr+1, arr, 12); // 12字节 = 3个int

期望的结果可能是 {1, 1, 2, 3, 5},但由于我们的 my_memcpy 是从低地址向高地址拷贝,在复制过程中,源数据在被读取之前就被覆盖了,导致结果出错。这种情况下,必须使用 memmove 函数

在这里插入图片描述

2. memmove使用和模拟实现

1. 函数是什么?为什么需要它?

memmove 是 C 语言标准库中另一个用于内存拷贝的函数,它与 memcpy 功能相似,但有一个关键的区别:memmove 能够正确处理源内存和目标内存重叠的情况

函数原型:

void *memmove(void *destination, const void *source, size_t num);
  • 参数与返回值:与 memcpy 完全相同
  • 核心优势:处理内存重叠时的安全性

在这里插入图片描述

2. 核心问题:什么是内存重叠?为什么 memcpy 处理不了?

让我们通过一个例子来理解这个问题:

#include <stdio.h>
#include <string.h>int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};// 场景:将数组前5个元素复制到从第3个元素开始的位置// 这会导致源区域 [0-4] 与目标区域 [2-6] 发生重叠memmove(arr + 2, arr, 20); // 20字节 = 5个int元素for (int i = 0; i < 10; i++) {printf("%d ", arr[i]);}// 输出:1 2 1 2 3 4 5 8 9 10return 0;
}

如果用 memcpy 会发生什么?

如果我们错误地使用 memcpy(arr + 2, arr, 20),由于 memcpy 只是简单地从低地址向高地址逐字节拷贝,会发生这样的悲剧:

  1. 先把 arr[0] (1) 拷贝到 arr[2] → 数组变成 [1, 2, 1, 4, 5, ...]
  2. 再把 arr[1] (2) 拷贝到 arr[3] → 数组变成 [1, 2, 1, 2, 5, ...]
  3. 接着把 arr[2] (现在已经是1了!) 拷贝到 arr[4] → 数组变成 [1, 2, 1, 2, 1, ...]

看到问题了吗?源数据在被读取之前就被覆盖了,导致复制结果完全错误!

3. memmove 的智能解决方案

memmove 通过判断内存重叠情况,智能地选择拷贝方向来解决这个问题:

  • 当目标地址在源地址之前,或者两者没有重叠时:从低地址向高地址拷贝(与 memcpy 相同)
  • 当目标地址在源地址之后,且存在重叠时:从高地址向低地址拷贝(反向拷贝)
4. 动手模拟实现

下面是 memmove 的模拟实现代码,体现了这种智能的拷贝策略:

#include <assert.h>void* my_memmove(void* dst, const void* src, size_t count) {void* ret = dst;// 安全检查assert(dst && src);// 情况1:目标地址在源地址之前,或者没有重叠// 从低地址向高地址拷贝(正向拷贝)if (dst <= src || (char*)dst >= (char*)src + count) {while (count--) {*(char*)dst = *(char*)src;dst = (char*)dst + 1;src = (char*)src + 1;}}// 情况2:目标地址在源地址之后,且存在重叠// 从高地址向低地址拷贝(反向拷贝)else {// 将指针移动到内存块的末尾dst = (char*)dst + count - 1;src = (char*)src + count - 1;while (count--) {*(char*)dst = *(char*)src;dst = (char*)dst - 1;src = (char*)src - 1;}}return ret;
}

实现要点解析:

  1. 重叠判断逻辑
    • dst <= src:目标在源之前,安全正向拷贝
    • (char*)dst >= (char*)src + count:目标在源结束之后,没有重叠,安全正向拷贝
    • 其他情况:目标在源之后且存在重叠,需要反向拷贝
  2. 反向拷贝技巧
    • 先将指针移动到内存块的最后一个字节:dst = (char*)dst + count - 1
    • 然后从后往前逐个字节拷贝
    • 这样确保重叠部分中尚未被读取的数据不会被覆盖

在这里插入图片描述

5. 验证实现

让我们用之前的例子来测试:

int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};printf("原始数组: ");for (int i = 0; i < 10; i++) printf("%d ", arr[i]);printf("\n");// 测试重叠拷贝my_memmove(arr + 2, arr, 20);printf("拷贝后数组: ");for (int i = 0; i < 10; i++) printf("%d ", arr[i]);printf("\n");return 0;
}

输出结果:

在这里插入图片描述

perfect!数据被正确地复制了,没有因为内存重叠而出错。

6. 使用建议
  1. 安全第一:当你不确定源内存和目标内存是否重叠时,优先使用 memmove
  2. 性能考量:如果确定两者不重叠,memcpy 可能稍微快一点(因为不需要做重叠判断)。
  3. 适用场景
    • 数组元素的移动
    • 缓冲区内部数据的调整
    • 任何可能涉及内存重叠的拷贝操作

在这里插入图片描述

3. memset函数的使用和模拟实现

1. 函数是什么?

memset 是 C 语言标准库中用于内存初始化批量设置的函数。它能够将指定内存区域的每个字节都设置为特定的值。

函数原型:

void *memset(void *ptr, int value, size_t num);
  • ptr: 指向要设置的内存区域的起始地址
  • value: 要设置的值(以 int 形式传递,但实际使用时会被转换为 unsigned char
  • num: 要设置的字节数
  • 返回值: 返回指向 ptr 的指针

在这里插入图片描述

2. 核心特性与工作原理
  • 按字节设置:这是 memset 最重要的特性。它不关心内存中存储的是什么数据类型,只是简单地将每个字节都设置为指定的值。
  • 高效批量操作:相比于使用循环逐个赋值,memset 通常经过高度优化,执行效率更高。
  • 用途广泛:常用于内存初始化、数组清零、字符串填充等场景。
3. 实战示例

让我们通过几个例子来理解它的用法:

示例1:字符串填充

#include <stdio.h>
#include <string.h>int main() {char str[] = "hello world";// 将前6个字符设置为'x'memset(str, 'x', 6);printf("%s\n", str);return 0;
}	

输出:

在这里插入图片描述

示例2:数组清零

#include <stdio.h>
#include <string.h>int main() {int arr[5] = {1, 2, 3, 4, 5};printf("清零前: ");for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}printf("\n");// 将整个数组清零memset(arr, 0, sizeof(arr));printf("清零后: ");for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}printf("\n");return 0;
}

输出:

在这里插入图片描述

4. 重要注意事项

陷阱:不要误解 memset 对整型数组的设置

这是一个常见的错误用法:

int arr[5];
// 错误:试图将每个元素设置为1
memset(arr, 1, sizeof(arr));

你以为的结果:{1, 1, 1, 1, 1}
实际的结果:每个 int 元素的每个字节都被设置为1

假设 int 占4字节,那么每个 int 元素的值将是:

00000001 00000001 00000001 00000001

转换为十进制就是:16843009,而不是1!

正确用法总结:

  • ✅ 清零:memset(arr, 0, sizeof(arr))
  • ✅ 设置为 -1:memset(arr, -1, sizeof(arr))(因为-1的二进制表示是所有位都是1)
  • ✅ 字符数组/字符串操作
  • ❌ 不要用于将整型数组设置为非0非-1的值
5. 动手模拟实现

让我们自己实现一个 memset 函数来加深理解:

void* my_memset(void* ptr, int value, size_t num) {// 保存原始指针,用于返回void* start = ptr;// 将value转换为unsigned char,确保只取低8位unsigned char byte_value = (unsigned char)value;// 将void*转换为unsigned char*以便逐字节操作unsigned char* p = (unsigned char*)ptr;// 逐字节设置内存for (size_t i = 0; i < num; i++) {p[i] = byte_value;}return start;
}

在这里插入图片描述

更优化的版本(使用指针运算):

void* my_memset_optimized(void* ptr, int value, size_t num) {unsigned char* p = (unsigned char*)ptr;unsigned char byte_value = (unsigned char)value;// 使用指针运算代替数组索引while (num--) {*p++ = byte_value;}return ptr;
}

实现要点解析:

  1. 类型转换:将 void* 转换为 unsigned char* 是关键,因为 unsigned char 正好是1字节,便于逐字节操作。
  2. 值处理:将传入的 int 值转换为 unsigned char,确保只使用低8位,避免意外行为。
  3. 循环方式:可以使用数组索引 p[i] 或指针运算 *p++,指针通常更加高效。
  4. 返回值:返回原始指针,支持链式调用。
6. 验证实现
#include <stdio.h>int main() {// 测试1:字符串填充char str[] = "hello world";my_memset(str, 'A', 5);printf("测试1: %s\n", str); // 输出: AAAAA world// 测试2:数组清零int arr[3] = {10, 20, 30};my_memset(arr, 0, sizeof(arr));printf("测试2: %d %d %d\n", arr[0], arr[1], arr[2]); // 输出: 0 0 0// 测试3:验证按字节设置的特点int test = 0;my_memset(&test, 1, sizeof(test));printf("测试3: %d (验证按字节设置)\n", test); // 输出: 16843009return 0;
} 

4. memcmp函数的使用和模拟实现

1. 函数是什么?

memcmp 是 C 语言标准库中用于比较两块内存区域内容的函数。它能够逐字节地比较两个内存块,判断它们是否相等或者哪个更大。

函数原型:

int memcmp(const void *ptr1, const void *ptr2, size_t num);
  • ptr1: 指向第一个内存块的指针
  • ptr2: 指向第二个内存块的指针
  • num: 要比较的字节数
  • 返回值: 比较结果,遵循特定的规则

在这里插入图片描述

2. 核心特性与工作原理
  • 按字节比较memcmp 逐个字节地比较两个内存区域,直到发现不同的字节或比较完所有指定字节
  • 不关心内容类型:与 memcpy 类似,它不关心内存中存储的是什么数据类型
  • 不依赖终止符:与 strcmp 不同,memcmp 遇到 '\0' 不会停止,它会比较完所有指定字节
  • 精确比较:能够比较任何二进制数据,包括结构体、数组等
3. 返回值规则详解

memcmp 的返回值规则很明确:

返回值含义
< 0第一个不匹配的字节在 ptr1 中的值 < 在 ptr2 中的值(按无符号字符解释)
= 0两个内存块的内容完全相等
> 0第一个不匹配的字节在 ptr1 中的值 > 在 ptr2 中的值(按无符号字符解释)

重要提示:比较时是按照 unsigned char 类型来解释每个字节的!

4. 实战示例

让我们通过几个例子来深入理解:

示例1:基本字符串比较

#include <stdio.h>
#include <string.h>int main() {char str1[] = "Hello";char str2[] = "Hello";char str3[] = "Hell0"; // 最后是数字0int result1 = memcmp(str1, str2, 5);int result2 = memcmp(str1, str3, 5);printf("比较 'Hello' 和 'Hello': %d\n", result1); // 输出: 0printf("比较 'Hello' 和 'Hell0': %d\n", result2); // 输出: >0的数字return 0;
}

示例2:区分大小写的比较

#include <stdio.h>
#include <string.h>int main() {char buffer1[] = "DWga0tP12df0";char buffer2[] = "DWGA0TP12DF0"; int n = memcmp(buffer1, buffer2, sizeof(buffer1));if (n > 0)printf("'%s' 大于 '%s'\n", buffer1, buffer2);else if (n < 0)printf("'%s' 小于 '%s'\n", buffer1, buffer2);elseprintf("'%s' 等于 '%s'\n", buffer1, buffer2);return 0;
}

输出:

在这里插入图片描述

这是因为小写字母的 ASCII 值大于对应的大写字母。

示例3:比较结构体

#include <stdio.h>
#include <string.h>typedef struct {int id;char name[20];float score;
} Student;int main() {Student s1 = {1, "Alice", 95.5};Student s2 = {1, "Alice", 95.5};Student s3 = {2, "Bob", 88.0};// 比较两个相同的学生int result1 = memcmp(&s1, &s2, sizeof(Student));printf("相同学生比较: %d\n", result1); // 输出: 0// 比较两个不同的学生int result2 = memcmp(&s1, &s3, sizeof(Student));printf("不同学生比较: %d\n", result2); // 输出: 非0值return 0;
}
5. 与 strcmp 的区别

理解 memcmpstrcmp 的区别很重要:

特性memcmpstrcmp
停止条件比较完指定字节数遇到 '\0' 终止符
参数需要指定比较的字节数自动根据字符串长度比较
适用范围任何内存数据(结构体、数组等)仅适用于以 '\0' 结尾的字符串
性能通常更快(不需要检查终止符)需要检查终止符
6. 动手模拟实现

让我们自己实现一个 memcmp 函数:

int my_memcmp(const void* ptr1, const void* ptr2, size_t num) {// 转换为 unsigned char* 以便逐字节比较const unsigned char* p1 = (const unsigned char*)ptr1;const unsigned char* p2 = (const unsigned char*)ptr2;// 逐字节比较for (size_t i = 0; i < num; i++) {if (p1[i] != p2[i]) {return (int)(p1[i]) - (int)(p2[i]);}}return 0;
}

更优化的指针版本:

int my_memcmp_optimized(const void* ptr1, const void* ptr2, size_t num) {const unsigned char* p1 = ptr1;const unsigned char* p2 = ptr2;while (num-- > 0) {if (*p1 != *p2) {return (*p1 > *p2) ? 1 : -1;}p1++;p2++;}return 0;
}

实现要点解析:

  1. 类型转换:转换为 unsigned char* 确保按字节比较,并且正确处理符号问题
  2. 比较逻辑:发现不同的字节立即返回,否则继续比较
  3. 返回值计算:返回第一个不同字节的差值,确保符合标准规定的正负号
  4. 边界处理:正确处理 num 为 0 的情况
7. 验证实现
#include <stdio.h>void test_comparison(const char* desc, const void* p1, const void* p2, size_t n) {int result1 = memcmp(p1, p2, n);int result2 = my_memcmp(p1, p2, n);printf("%s:\n", desc);printf("  标准 memcmp: %d\n", result1);printf("  我们的实现: %d\n", result2);printf("  结果一致: %s\n\n", (result1 == result2) ? "是" : "否");
}int main() {// 测试1:相同字符串char str1[] = "Hello";char str2[] = "Hello";test_comparison("相同字符串", str1, str2, 5);// 测试2:不同字符串char str3[] = "Hello";char str4[] = "Hell0";test_comparison("不同字符串", str3, str4, 5);// 测试3:数组比较int arr1[] = {1, 2, 3};int arr2[] = {1, 2, 4};test_comparison("数组比较", arr1, arr2, sizeof(arr1));// 测试4:部分比较test_comparison("部分比较", "ABCDE", "ABCDF", 4); // 只比较前4个字节return 0;
}

结语

通过本篇文章的学习,我们学习了C语言中四大内存函数:memcpymemmovememsetmemcmp

大家不仅要会使用这些函数,还要理解每个函数的工作原理,自己也可以通过模拟实现来加深理解,并且要时刻注意内存边界和重叠问题,在不确定时优先选择更安全的函数!

那么本篇内容到这里就结束了,希望大家能将所学知识灵活运用到实际编程中,在C语言的道路上稳步前行!

最后分享一段我非常喜欢的话送给大家:

每个优秀的人,都有一段沉默的时光。那段时光,是付出了很多努力,却得不到结果的日子,我们把他叫做扎根。

http://www.dtcms.com/a/422756.html

相关文章:

  • 学习React-19-useDebugValue
  • Python实现网站/网页管理小工具
  • 魏公村网站建设城阳做网站的公司
  • 【笔记】介绍 WPF XAML 中 Binding 的 StringFormat详细功能
  • 【XR行业应用】XR + 医疗:重构未来医疗服务的技术革命与实践探索
  • 微服务-Nacos 技术详解
  • 天津高端网站建设企业旅游品牌网站的建设
  • 51单片机-实现DAC(PWM)数模转换PWM控制呼吸灯、直流电机实验教程
  • VMware安装虚拟机并且部署 CentOS 7 指南
  • 响应网站先做电脑端网站更换空间后排名消失 首页被k
  • 怎么做跳转网站首页化妆品网站制作
  • 数据结构 排序(3)---交换排序
  • 告别内网困局:cpolar破解Websocket远程访问难题
  • LeetCode热题--207. 课程表--中等
  • 算法奇妙屋(四)-归并分治
  • 【嵌入式硬件实例】-晶体管放大器
  • ArcGIS数据迁移问题汇总(postgresql)
  • SQLyog:一款功能强大、简单易用的MySQL管理工具
  • Boost 搜索引擎
  • 紧跟大数据技术趋势:食物口味分析系统Spark SQL+HDFS最新架构实现
  • c2c网站的建设做企业网站有哪些好处
  • 从AI角度深入解析和论述哲学的终极答案‘语言即世界‘
  • 网站开发实训报告织梦手机网站建设
  • python做网站开发免费公司企业建站代理
  • 科技信息差(9.29)
  • ES如何基于Linux配置开机自启动
  • DeepSeek Java 单例模式详解
  • 单例模式入门
  • PMON failed to acquire latch 的报错及sqlplus / as sysdba 无法连接
  • Vibe Coding - MCP Feedback Enhanced(交互反馈 MCP)