【C】C语言内存操作函数的解析

C语言内存操作函数实战解析:malloc、memcpy、memset与内存安全 内存管理是C语言编程的核心基础,涉及程序运行时的数据存储与访问。其原理在于程序员直接操作物理或虚拟内存地址,通过分配、读写和释放内存块来控制数据生命周期。这项技术的价值在于提供了无与伦比的性能与控制力,是系统软件、嵌入式开发和高性能计算的基石。应用场景广泛,从操作系统内核、设备驱动到游戏引擎和数据库系统都离不开精细的内存操作。然而,不当使用会导致段错误、内存泄漏等严重问题。本文聚焦于内存操作的关键工具,特别是`malloc`、`memcpy`和`memset`等函数,深入探讨其正确使用方法和常见陷阱。例如, 阅读详情

0. 前言

        当我们有一个字符char str1[] = "hello world!",此时,我们要新建一个字符串str2,想让str2的内容和str1一模一样,可以使用字符串函数strcpy,对两个字符串进行比较,可以使用strcmp,对字符串的操作,C语言内置的库已经给我们把解决方案都罗列好了。

        但是,如果我们已经有了一个数组int arr1[],新建一个数组arr2,让arr2的内容和arr1完全一样呢,这个时候又该怎么办呢?

// 怎么让cp_arr的内容和arr完全一样?
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int cp_arr[10] = { 0 };

  我们是否要这样做:

void PrintArr(int* arr, size_t num)
{
	for (size_t i; i < num; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	int cp_arr[20] = { 0 };

	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
	{
		cp_arr[i] = arr[i];
	}
	printf("arr: ");
	PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
	
	printf("cp_arr: ");
	PrintArr(arr, sizeof(cp_arr) / sizeof(cp_arr[0]));

	return 0;
}

这种方法可行,但未免也太过麻烦。

        C语言在标准库里早已经为我们实现好了处理这类情况的解决方案,他们都是对内存块进行操作,这类接口都是以mem开头,称为内存操作函数。

最核心最标准的内存操作函数有如下几种:

  • memcpy内存拷贝(不处理重叠内存,效率高)
  • memmove内存移动(处理重叠内存,更安全)
  • memset内存填充(批量设置字节,常用初始化 / 清零)
  • memcmp内存比较(按字节对比两块内存)
  • memchr内存查找(在内存块中搜索指定字节)

而memcpy就是专门解决类似刚刚那种内存拷贝的问题:

int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int cp_arr[10] = { 0 };

/*for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
	cp_arr[i] = arr[i];
}*/

memcpy(cp_arr, arr, sizeof(arr));

printf("arr: ");
PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

printf("cp_arr: ");
PrintArr(cp_arr, sizeof(cp_arr) / sizeof(cp_arr[0]));

return 0;

运行结果:

下面,我们来对这些内存操作函数进行详细了解。

1. 内存操作函数

        mem开头的这些内存操作函数需要包含统一的头文件<string.h>(C++中是<cstring>),虽然他们是内存操作函数,但还是把他们归到了<string.h>中。

        内存操作函数的单位一定是字节,这一点要牢记,你指定多少字节,就给你操作多少字节

1.1 memcpy

1.1.1 函数功能介绍与使用

功能:将 source 指向的内存块,直接复制 num 个字节到 destination 指向的内存块。

参数介绍:

参数含义
destination目标内存地址(可被转换为void*类型的指针)
source源数据内存地址(可被转换为const void*类型的指针)
num要复制的字节数,类型为无符号整数size_t

使用:

先定义一个测试用的结构体:

// 定义一个测试用结构体
struct Student {
    int id;
    int age;
    char name[20];
};

可以拷贝int数组:

// ==========================
// 1. memcpy 拷贝 int 数组
// ==========================
int arr1[5] = { 10, 20, 30, 40, 50 };
int arr2[5] = { 0 }; // 初始化为0

// 拷贝 5个int 的内存
memcpy(arr2, arr1, 5 * sizeof(int));

printf("===== int数组拷贝 =====\n");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
}
printf("\n\n");

        也可以拷贝char数组,拷贝char数组要注意一个问题,他一定是按照你指定的字节数去拷贝的,不会给你加上\0。

// 3. memcpy 拷贝 char数组
// 【重点:不处理 \0,只按字节拷贝】
// ==========================
char str1[20] = "hello world"; // 自带 \0
char str2[20] = "---------------"; // 用来观察覆盖效果

printf("===== char数组拷贝(重点:\\0问题) =====\n");
printf("拷贝前 str2 = %s\n", str2);

// 只拷贝 5 个字节:'h','e','l','l','o'
// 注意:没有拷贝字符串末尾的 \0 !!!
memcpy(str2, str1, 5 * sizeof(char));

printf("拷贝后 str2 = %s\n", str2);
printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");

        还可以拷贝结构体:

// ==========================
// 2. memcpy 拷贝 结构体
// ==========================
struct Student stu1 = { 1001, 20, "ZhangSan" };
struct Student stu2;

// 直接拷贝整个结构体的内存
memcpy(&stu2, &stu1, sizeof(struct Student));

printf("===== 结构体拷贝 =====\n");
printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);

由此可见,memcpy的操作他是这样的,图示:

1.1.2 memcpy的模拟实现

        如果这个函数功能要由我们自己来实现呢,对一个接口进行模拟实现,首先看的就是他的参数和返回值,参数这里不再赘述,memcpy的返回值是:

        也就是目标内存块的起始地址

        对memcpy的模拟实现,他既然要操作num个字节,要想一个字节一个字节的操作,我们肯定是要借助char*,实现如下:

void* my_memcpy(void* des, const void* src, size_t num)
{
    // 你给我传进来的两个指针不能为空
    assert(des && src);

    // 一个字节一个字节拷贝,总共有num个字节
    for (size_t i = 0; i < num; ++i)
        ((char*)des)[i] = ((char*)src)[i];

    // 返回值要是目标内存块的起始地址
    return des;
}

        这个函数的模拟实现整体还是非常简单的,测试一下,将刚刚的测试用例全部换成我们自己实现的memcpy:

// ==========================
// 1. memcpy 拷贝 int 数组
// ==========================
int arr1[5] = { 10, 20, 30, 40, 50 };
int arr2[5] = { 0 }; // 初始化为0

// 拷贝 5个int 的内存
my_memcpy(arr2, arr1, 5 * sizeof(int));

printf("===== int数组拷贝 =====\n");
for (int i = 0; i < 5; i++) {
    printf("%d ", arr2[i]);
}
printf("\n\n");


// ==========================
// 2. memcpy 拷贝 结构体
// ==========================
struct Student stu1 = { 1001, 20, "ZhangSan" };
struct Student stu2;

// 直接拷贝整个结构体的内存
my_memcpy(&stu2, &stu1, sizeof(struct Student));

printf("===== 结构体拷贝 =====\n");
printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);


 //==========================
 //3. memcpy 拷贝 char数组
 //【重点:不处理 \0,只按字节拷贝】
 //==========================
char str1[20] = "hello world"; // 自带 \0
char str2[20] = "---------------"; // 用来观察覆盖效果

printf("===== char数组拷贝(重点:\\0问题) =====\n");
printf("拷贝前 str2 = %s\n", str2);

// 只拷贝 5 个字节:'h','e','l','l','o'
// 注意:没有拷贝字符串末尾的 \0 !!!
my_memcpy(str2, str1, 5 * sizeof(char));

printf("拷贝后 str2 = %s\n", str2);
printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");

        结果非常完美,没有任何问题。

        但是但是,如果说我们要做的操作是这样呢?

        我想做的是一个内存块内部的拷贝,那我们刚刚实现的memcpy还能解决这个问题吗?

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    my_memcpy(arr + 2, arr, sizeof(int) * 5);

    return 0;
}

        很显然并不能,因为把每次拷贝的细节掰开来看的话,当我们将1拷贝到3, 2拷贝到4的位置的时候,原本的值就被覆盖了,等再想把3拷贝到5这个位置的时候,3已经不见了,取而代之的刚刚拷贝过来的1,于是就会出现这样的情况。

        在这种情况下,我们就引出了下面这个内存操作函数-----memmove

1.2 memmove

1.2.1 函数功能介绍与使用

返回值:返回目标内存块的起始地址。

功能:从 source 拷贝 num 个字节到 destination,支持处理内存重叠场景。

他的参数和memcpy一模一样,都是对内存块的操作,唯一的不同是:memmove 是一个支持内存重叠的安全版 memcpy,拷贝逻辑更鲁棒,代价是比 memcpy 略慢(多了方向判断和分支)。

函数的使用:

1. 将刚刚的情况换成memmove,还会出现刚刚的问题吗?

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    memmove(arr + 2, arr, sizeof(int) * 5);
    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

    return 0;
}

答案是当然不会

 2. 再看一段测试用例:

int main()
{
    // 测试用数组(内存重叠场景)
    char arr[] = "abcdefghijklmn";

    printf("原字符串:%s\n", arr);

    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
    // 源和目标内存重叠,必须用 memmove!
    memmove(arr, arr + 2, 5);

    printf("移动后:%s\n", arr);

    return 0;
}

 memmove = 能安全处理内存重叠的 memcpy这就是它唯一、也是最重要的功能!

那这个函数,怎么进行模拟实现呢?

1.2.2 memmove的模拟实现

        memmove的模拟实现,牢记一个点,他的实现就会变得非常简单。在一个内存块上对数据的操作,势必会造成覆盖这种问题,从而导致结果出错,当你就是要做重叠操作的时候,覆盖是一定会发生的,我们可以发送覆盖,但要切记:

        一定不要覆盖,你将来会使用的数据。将来这个词很关键,也就是从侧面说明,你可以覆盖你已经用过的数据,这样结果就不会出问题了。

        所以刚刚那个例子,我们可以这样去操作:

        从后往前拷贝,这样不就不会被覆盖了吗。

        可是,一味的从后往前拷贝,就不会出问题吗?答案是否定的,一定会出问题,看下面这种场景:

        我想把后面的3,4,5,6,7,挪到数组最开始的位置,变成3,4,5,6,7,5,6,8,9,10。

从后往前挪?能做到吗?

        这种情况是做不到的,因为在7挪到5的位置的时候,5还没有被使用,这就造成了破坏性的覆盖,6的道理也是一样的。所以这种情况下从后往前挪就不行。

        因此,在这种情况下,需要从前往后挪,先挪3,7最后挪,中间当5把3覆盖的时候,3已经被使用,不怕覆盖,6覆盖4,7覆盖5也是同样的道理。

        经过上述分析,memmove的模拟实现的总体思路就已经全部出来了,非常直观:

        当dst在src之前的时候,我们从前往后拷贝,不怕覆盖,当dst在src之后的时候,我们从后往前拷贝,也不怕覆盖:

void* my_memmove(void* des, const void* src, size_t num)
{
    assert(des && src);

    // memmove的模拟实现,主要就是抓住一个点:你不要覆盖自己将来可能会用到的数据

    // 当des再src之前,正常从前往后拷贝
    if ((char*)des < (char*)src)
    {
        for (size_t i = 0; i < num; ++i)
            ((char*)des)[i] = ((char*)src)[i];
    }
    else
    {
        // 这个地方的i一定不能用size_t类型,要不然i>=0的边界是一定会出问题的,会死循环
        for (int i = num - 1; i >= 0; --i)
            ((char*)des)[i] = ((char*)src)[i];
    }

    return des;
}

        测试一下:

int main()
{
    // 测试用数组(内存重叠场景)
    char arr1[] = "abcdefghijklmn";

    printf("原字符串:%s\n", arr1);

    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
    // 源和目标内存重叠,必须用 memmove!
    my_memmove(arr1, arr1 + 2, 5);

    printf("移动后:%s\n", arr1);

    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    my_memmove(arr + 2, arr, sizeof(int) * 5);
    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));

    return 0;
}

        没有任何问题,完美实现

        从模拟实现也可以看出来,就算内存没有重叠,memmove也完全可用,他比memcpy更安全,就是会稍微慢一点,毕竟安全的代价当然就是实现起来更复杂一点点。

1.3 一种现象

        看这段代码:

int main()
{
    // 同一数组,内存高度重叠
    char arr[30] = "abcdefghijklmn";

    printf("修改前:%s\n", arr);

    // 危险操作:目标地址 < 源地址,内存重叠!
    // 标准说 memcpy 行为未定义
    memcpy(arr, arr + 2, 5);

    printf("修改后:%s\n", arr);

    return 0;
}

        这是C标准库里实现的memcpy,可以很明显的看到,memcpy对重叠内存块的操作,也是没有任何问题的,由此我们可以大胆推测:

        C标准库的memcpy的实现,是采用的memmove的实现方式,他给你把重叠问题也给处理了。

        这种实现是没有任何问题的,memcpy只需要把内存块不重叠的情况解决就可以了,但是他帮你把重叠的也给解决了。就是说老师只需要你考60分,但是你考了100分,行还不是不行?太行了!所以这一点大家也要注意

        剩下的内存操作函数,memset,cmp,chr就很简单了,功能,作用都很单一。

1.4 memset

返回值:返回内存块的起始地址 ptr。

核心功能:将 ptr 指向的内存块的前 num 个字节,全部设置为 value 对应的字节值(按 unsigned char 解释)。

逐字节填充:它是按字节操作的,value 最终会被截断为 unsigned char 类型,只取低 8 位。比如 memset(arr, 0x1234, 4),实际填充的是 0x34 这个字节。这是历史的产物,也可以说是C语言的一个小缺陷。

无视类型:不管 ptr 指向的是 char[]、int[] 还是结构体,都按字节填充,因此常用来初始化 / 清零内存。

无终止符检查:和 memcpy 一样,只看 num 字节,不管 \0。

典型使用场景:

int main()
{
    int arr[5];
    // 1. 数组清零(最常用)
    memset(arr, 0, sizeof(arr));

    char str[10];
    // 2. 填充指定字符
    memset(str, '#', sizeof(str)-1);
    str[9] = '\0'; // 手动补结束符

    printf("str = %s\n", str);
    return 0;
}

        模拟实现:

void* my_memset(void* ptr, int value, size_t num)
{
    assert(ptr);
    unsigned char* p = (unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        p[i] = val;
    }
    return ptr;
}

正确用法:只用来清零或填充单字节值。

1.5 memcmp

功能 :比较 ptr1 和 ptr2 指向的两块内存的前 num 个字节,按无符号字符逐字节对比,返回表示大小关系的结果。

关键特性:

不终止于 \0:与 strcmp 最大的区别是,它不会因为遇到 \0 就停止比较,会严格对比指定的 num 个字节。

按字节无符号比较:比较时按 unsigned char 解释每个字节的值,因此 0xFF 会被认为大于 0x00。

返回值含义:

返回值含义
< 0ptr1 中第一个不匹配的字节值小于 ptr2 中的对应字节
= 0两块内存的前 num 个字节完全相同
> 0ptr1 中第一个不匹配的字节值大于 ptr2 中的对应字节

使用场景:

int main()
{
    char str1[] = "abc\0def";
    char str2[] = "abc\0xyz";

    // 对比前 5 个字节,不受中间 \0 影响
    int ret = memcmp(str1, str2, 5);

    if (ret == 0)
        printf("两块内存前5个字节相同\n");
    else if (ret < 0)
        printf("str1 小于 str2\n");
    else
        printf("str1 大于 str2\n");

    return 0;
}

模拟实现:

int my_memcmp(const void *ptr1, const void *ptr2, size_t num)
{
    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 p1[i] - p2[i];
        }
    }
    return 0;
}

注意!!!

不要用 memcmp 比较包含指针、浮点数等复杂类型的结构体:这些类型的字节表示可能有填充、对齐或实现差异,即使逻辑上相等,字节对比也可能失败。

只适合比较原始字节数据(如字节数组、字符串内容)。

1.6 memchr

功能:在 ptr 指向的内存块的前 num 个字节中,按无符号字节查找第一个与 value 匹配的字节,找到则返回该字节的地址,找不到返回 NULL。

关键特性

逐字节无符号比较:value 会被截断为 unsigned char 再参与比较,因此 memchr(arr, 0x1234, ...) 实际查找的是 0x34。

无视 \0:和 strchr 最大的区别是,它不会因为遇到字符串结束符 \0 就停止,会严格搜索指定的 num 个字节。

找到即返回:只返回第一个匹配项的地址。

使用场景:

int main()
{
    char str[] = "abc\0defghij";

    // 在前 8 个字节中查找字符 'd'
    char *p = (char*)memchr(str, 'd', 8);

    if (p != NULL)
        printf("找到了:%s\n", p);
    else
        printf("没找到\n");

    return 0;
}

模拟实现:

void* my_memchr(const void *ptr, int value, size_t num)
{
    const unsigned char *p = (const unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        if (p[i] == val)
        {
            return (void*)(p + i);
        }
    }
    return NULL;
}

2. 总结

函数核心功能关键特性
memcpy内存拷贝不处理重叠,效率高
memmove内存移动支持重叠拷贝,更安全
memset内存填充按字节设置值,常用于清零
memcmp内存比较逐字节对比,无视 \0
memchr内存查找按字节搜索,无视 \0

这五个函数的本质都是按字节操作内存,区别只在于:

拷贝 / 移动:处理数据位置

填充:批量设置字节值

比较 / 查找:逐字节对比和搜索

它们都无视数据类型,也不关心字符串结束符 \0,这是和字符串函数最核心的区别。

附录:完整代码

#define _CRT_SECURE_NO_WARNINGS 1

#include <stdio.h>
#include <string.h>

//int main()
//{
//	char str1[] = "hello world!";
//	char str2[20];
//	printf("str1 = %s\n", str1);
//	strcpy(str2, str1);
//	printf("str2 = %s\n", str1);
//
//	return 0;
//}

void PrintArr(int* arr, size_t num)
{
	for (size_t i = 0; i < num; ++i)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

//int main()
//{
//
//
//	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//	int cp_arr[10] = { 0 };
//
//	/*for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
//	{
//		cp_arr[i] = arr[i];
//	}*/
//
//	memcpy(cp_arr, arr, sizeof(arr));
//
//	printf("arr: ");
//	PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//	
//	printf("cp_arr: ");
//	PrintArr(cp_arr, sizeof(cp_arr) / sizeof(cp_arr[0]));
//
//	return 0;
//}

#include <assert.h>
void* my_memcpy(void* des, const void* src, size_t num)
{
    // 你给我传进来的两个指针不能为空
    assert(des && src);

    // 一个字节一个字节拷贝,总共有num个字节
    for (size_t i = 0; i < num; ++i)
        ((char*)des)[i] = ((char*)src)[i];

    // 返回值要是目标内存块的起始地址
    return des;
}

//// 定义一个测试用结构体
//struct Student {
//    int id;
//    int age;
//    char name[20];
//};
//
//int main() {
//    // ==========================
//    // 1. memcpy 拷贝 int 数组
//    // ==========================
//    int arr1[5] = { 10, 20, 30, 40, 50 };
//    int arr2[5] = { 0 }; // 初始化为0
//
//    // 拷贝 5个int 的内存
//    my_memcpy(arr2, arr1, 5 * sizeof(int));
//
//    printf("===== int数组拷贝 =====\n");
//    for (int i = 0; i < 5; i++) {
//        printf("%d ", arr2[i]);
//    }
//    printf("\n\n");
//
//
//    // ==========================
//    // 2. memcpy 拷贝 结构体
//    // ==========================
//    struct Student stu1 = { 1001, 20, "ZhangSan" };
//    struct Student stu2;
//
//    // 直接拷贝整个结构体的内存
//    my_memcpy(&stu2, &stu1, sizeof(struct Student));
//
//    printf("===== 结构体拷贝 =====\n");
//    printf("学号:%d\n年龄:%d\n姓名:%s\n\n", stu2.id, stu2.age, stu2.name);
//
//
//     //==========================
//     //3. memcpy 拷贝 char数组
//     //【重点:不处理 \0,只按字节拷贝】
//     //==========================
//    char str1[20] = "hello world"; // 自带 \0
//    char str2[20] = "---------------"; // 用来观察覆盖效果
//
//    printf("===== char数组拷贝(重点:\\0问题) =====\n");
//    printf("拷贝前 str2 = %s\n", str2);
//
//    // 只拷贝 5 个字节:'h','e','l','l','o'
//    // 注意:没有拷贝字符串末尾的 \0 !!!
//    my_memcpy(str2, str1, 5 * sizeof(char));
//
//    printf("拷贝后 str2 = %s\n", str2);
//    printf("原因:memcpy 只拷贝 5 个字节,不会自动加 \\0,后面的内容保留!\n");
//
//    return 0;
//}

//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memcpy(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

//int main()
//{
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    memmove(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

void* my_memmove(void* des, const void* src, size_t num)
{
    assert(des && src);

    // memmove的模拟实现,主要就是抓住一个点:你不要覆盖自己将来可能会用到的数据

    // 当des再src之前,正常从前往后拷贝
    if ((char*)des < (char*)src)
    {
        for (size_t i = 0; i < num; ++i)
            ((char*)des)[i] = ((char*)src)[i];
    }
    else
    {
        // 这个地方的i一定不能用size_t类型,要不然i>=0的边界是一定会出问题的,会死循环
        for (int i = num - 1; i >= 0; --i)
            ((char*)des)[i] = ((char*)src)[i];
    }

    return des;
}

//int main()
//{
//    // 测试用数组(内存重叠场景)
//    char arr1[] = "abcdefghijklmn";
//
//    printf("原字符串:%s\n", arr1);
//
//    // 核心:从 arr+2 拷贝 5 个字符到 arr 位置
//    // 源和目标内存重叠,必须用 memmove!
//    my_memmove(arr1, arr1 + 2, 5);
//
//    printf("移动后:%s\n", arr1);
//
//    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//    my_memmove(arr + 2, arr, sizeof(int) * 5);
//    PrintArr(arr, sizeof(arr) / sizeof(arr[0]));
//
//    return 0;
//}

//int main()
//{
//    // 同一数组,内存高度重叠
//    char arr[30] = "abcdefghijklmn";
//
//    printf("修改前:%s\n", arr);
//
//    // 危险操作:目标地址 < 源地址,内存重叠!
//    // 标准说 memcpy 行为未定义
//    memcpy(arr, arr + 2, 5);
//
//    printf("修改后:%s\n", arr);
//
//    return 0;
//}

//int main()
//{
//    int arr[5];
//    // 1. 数组清零(最常用)
//    memset(arr, 0, sizeof(arr));
//
//    char str[10];
//    // 2. 填充指定字符
//    memset(str, '#', sizeof(str) - 1);
//    str[9] = '\0'; // 手动补结束符
//
//    printf("str = %s\n", str);
//    return 0;
//}

//void* my_memset(void* ptr, int value, size_t num)
//{
//    assert(ptr);
//    unsigned char* p = (unsigned char*)ptr;
//    unsigned char val = (unsigned char)value;
//
//    for (size_t i = 0; i < num; ++i)
//    {
//        p[i] = val;
//    }
//    return ptr;
//}

//int main()
//{
//    char str1[] = "abc\0def";
//    char str2[] = "abc\0xyz";
//
//    // 对比前 5 个字节,不受中间 \0 影响
//    int ret = memcmp(str1, str2, 5);
//
//    if (ret == 0)
//        printf("两块内存前5个字节相同\n");
//    else if (ret < 0)
//        printf("str1 小于 str2\n");
//    else
//        printf("str1 大于 str2\n");
//
//    return 0;
//}

int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
    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 p1[i] - p2[i];
        }
    }
    return 0;
}

int main()
{
    char str[] = "abc\0defghij";

    // 在前 8 个字节中查找字符 'd'
    char* p = (char*)memchr(str, 'd', 8);

    if (p != NULL)
        printf("找到了:%s\n", p);
    else
        printf("没找到\n");

    return 0;
}

void* my_memchr(const void* ptr, int value, size_t num)
{
    const unsigned char* p = (const unsigned char*)ptr;
    unsigned char val = (unsigned char)value;

    for (size_t i = 0; i < num; ++i)
    {
        if (p[i] == val)
        {
            return (void*)(p + i);
        }
    }
    return NULL;
}

C语言内存操作函数深度解析:从memcpy到memmove的内存重叠处理 内存操作是C/C++编程的核心基础,理解其底层原理对编写高效、安全的代码至关重要。内存拷贝函数如memcpy通过直接操作内存地址实现数据的高效传输,其本质是字节级的搬运工。然而,当源内存与目标内存区域存在重叠时,简单的顺序拷贝会导致数据污染,这是内存操作中一个经典且易被忽视的边界问题。为了解决这一问题,标准库引入了memmove函数,它通过智能判断重叠方向(正向或反向)并选择相应的拷贝顺序(从低地址到高地址或反之),确保了重叠拷贝的正确性。这种对内存布局和拷贝顺序的精细控制,体现了底层编程对程序鲁棒性和严谨 阅读详情

相关推荐

C语言内存操作函数深度解析:从strcpy到memmove的原理与实现

内存操作是计算机编程中的基础概念,它涉及程序如何直接读写和管理计算机内存中的数据。其核心原理是通过指针访问内存地址,对连续的数据块进行复制、比较、设置等操作。掌握内存操作技术对于编写高效、安全的底层代码至关重要,尤其在系统编程、嵌入式开发和性能优化等场景中。本文聚焦C语言中的strcpy、memcpy等关键函数,深入探讨其内部机制与常见陷阱,并通过模拟实现帮助开发者从原理层面理解这些基础工具,从而避免缓冲区溢出等安全隐患,提升代码质量。

weixin_42565652的博客 237

C语言内存操作函数解析(mem系列函数)

本文介绍了C语言中常用的内存操作函数:memcpy(高效内存复制,不处理重叠)、memmove(安全内存移动,处理重叠)、memset(内存设置)、memcmp(内存比较)和memchr(内存字符搜索)。重点对比了memcpy和memmove的区别,前者追求效率但无法处理内存重叠,后者更安全但可能稍慢。文章通过函数原型、使用示例和实现代码详细说明了各函数的用法和特性,并提供了总结表格以帮助理解不同函数的使用场景。这些函数都是C语言中处理内存的基础工具,合理使用可以提升程序效率和安全性。

weixin_68492410的博客 755

AD从原理图到PCB超详细教程

我在学习AD的时候,看了网上很多教程,但是东西很杂,有很多条理不清而且废话很多,所以相信大家学习的时候也是遇到一样的困难。我就特地写一篇帖子,整理了一下AD从画原理图开始一直到画完PCB文件的所有步骤,一方面是供大家参考学习,另一方面可以方便我自己学习记忆更加深刻。我用的版本是AD16中文版,大家用新的版本AD20或者AD22应该操作都是差不多的。

人生寂寞如雪 18万+

C语言内存操作函数解析和模拟实现

文章目录内存操作函数memsetmemcpymemmovememcmp 内存操作函数 memset C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的空间的前 n 个字节。 头文件 <string.h> 声明 void *memset(void *str, int c, size_t n) 参数1 str – 指向要填充的内存块 参数2 c – 要被设置的值。该值以 int.

平凡的指针的博客 359

【报错】raise pickle.UnpicklingError(_get_wo_message(str(e))) from None_pickle.UnpicklingError: Weigh

File "/opt/conda/envs/py_3.12/lib/python3.12/site-packages/torch/serialization.py", line 1470, in load raise pickle.UnpicklingError(_get_wo_message(str(e))) from None _pickle.UnpicklingError: Weights only load failed.

静谧、淡雅 783

R语言LASSO回归作图为什么X轴使用-log(λ)而不是log(λ),如何转化为log(λ)?

🏆本文收录于 《全栈Bug调优(实战版)》 专栏,该专栏专注于分享我在真实项目开发中遇到的各类疑难Bug及其深层成因,并系统提供高效、可复现的解决思路和实操方案。无论你是刚入行的新手开发者,还是拥有多年项目经验的资深工程师,本专栏都将为你提供一条系统化、高质量的问题排查与优化路径,助力你加速成长,攻克技术壁垒,迈向技术价值最大化与职业发展的更高峰🚀!

**My Coding Family** 1015

C语言内存与字符串操作函数全解析

可以快速验证这些函数的实际效果,平台提供的一键部署功能让我在测试内存操作时省去了配置环境的麻烦。特别是演示memmove处理内存重叠的场景时,直接修改代码后实时看到运行结果非常方便。

QuartzLynx65的博客 414

使用 ChatGPT Store 里的工具给 ABAP 制作 Logo,效果一般

在编程领域中,Domain Specific Language(DSL)是一种特定于某一问题领域的计算机语言,诞生目的就是为了解决该领域特定类型的问题。在 ChatGPT 官网的 GPT Stores,也有很多已经预先配置完毕,专用于某一特定领域的 GPT 工具,OpenAI 将其分类如下。第三个问题是 Logo 的配色,提供蓝白,黑绿和灰黄,以及自定义调色板四种选择。这个 GPT 设计了四个问题,通过一问一答的方式,获得使用者想创建 Logo 的细节。这次的复杂度调成最低的 1,感觉还是有点花里胡哨的。

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 511

《深入理解 C 语言内存操作函数:memcpy、memmove、memset 与 memcmp 全解析》

memcpy函数的原型如下:该函数用于将source指向的内存地址开始的num个字节数据复制到指向的内存地址。注意memcpy不会因为遇到'\0'而停止复制,且如果源地址与目标地址存在重叠,其行为是未定义的。memmove函数的原型与memcpy相同:与memcpy不同的是,memmove可以处理内存重叠的情况。它是更安全的复制函数。memset函数的原型如下:该函数用于将ptr指向的内存区域的前num个字节设置为value的值(按字节设置memcmp函数的原型如下:该函数用于比较ptr1。

月夜的风吹雨的博客 1117

【Proteus仿真】基于AT89C51单片机的单片机双向通信

本文介绍了基于AT89C51单片机实现双向通信的系统设计。系统采用MAX232芯片实现TTL与RS-232电平转换,通过CONN-D9F接口建立串行通信连接。甲机程序通过按键控制发送不同指令字符,同时接收乙机数据驱动数码管显示;乙机程序根据接收指令控制LED显示状态,并可发送0-10数字至甲机。文中详细说明了MAX232电平转换原理、DB9接口定义,以及完整的单片机控制程序代码,包括串口初始化、数据收发处理等关键功能实现,为嵌入式系统串口通信开发提供了实用参考方案。

XU157303764的博客 1094

学Simulink--基础储能管理场景实例:基于Simulink的储能SOC均衡控制策略仿真

本文完成了基于 Simulink 的储能 SOC 均衡控制策略仿真,实现了:✅掌握并联系统 SOC 分化机理✅构建基于电流重分配的主动均衡架构✅验证均衡对系统可用容量的显著提升✅实现 SOC 偏差 < 2% 的高精度控制。

amy_mhd的博客 85

C语言内存操作函数深度解析:从memcpy到memmove的实践指南

特别是部署后的演示页面,非常适合用来对比展示memcpy和memmove的行为差异,这种可视化学习方式比单纯看代码更易理解。作为通用内存拷贝函数,memcpy通过void*指针实现任意数据类型的内存块复制,其核心优势在于不依赖具体数据类型。函数通过指定字节数控制复制范围,但需注意其遇到\0不会自动终止的特性,这与字符串函数有本质区别。memmove通过智能判断拷贝方向解决重叠问题:当检测到dest<src时采用前向拷贝,否则使用(num-1)偏移量的逆向拷贝策略。正确用法是配合字符数组或清零操作。

GoldenleafTiger89的博客 425

esp8266步进电机驱动控制

blinker控制步进电机起保停,正反转(Arduino uno+esp8266+TB6600驱动器) Arduino uno的板子的程序 esp8266板子接入到Arduino uno的控制引脚上的5,6,7 esp8266程序 //****库***** #define BLINKER_WIFI //官方wifi协议库 #define BLINKER_PRINT Serial //串口协议库 #include <Blinker.h&gt

perseverance51博客 2855

11.C语言内存管理与常用内存操作函数解析

本文深入探讨了C语言的内存管理,包括栈和堆的概念,用户手动与系统管理的内存差异。重点介绍了常用的内存操作函数,如malloc、calloc、realloc和free,并讲解了如何使用void指针进行灵活的内存管理。此外,文章还涉及了restrict说明符的使用,以及内存复制与比较函数如memcpy、memmove和memcmp的具体应用,帮助程序员有效掌控内存的使用与优化。

余识- 1355

【教程4>第8章>第44节】本章整体思维导图与学习总结

本教程系列提供FPGA/MATLAB/Simulink学习资源,包含《MATLAB入门100例》、《FPGA入门100例》、《Simulink入门60例》等基础教程,以及《FPGA/MATLAB/Simulink联合开发》进阶教程和《人工智能基础应用研究》专题,适合不同层次的学习者系统掌握相关技术。

FPGA/MATLAB学习教程/源码/项目合作开发 448

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

C语言内存操作函数深度解析:malloc、memcpy、memset与避坑指南

内存管理是系统编程与底层开发的核心基础,尤其在C语言这类手动管理内存的语言中,理解内存的分配、复制与初始化原理至关重要。从概念上讲,程序运行时内存分为栈、堆等区域,堆内存的动态管理需借助特定函数。其技术价值在于,高效、安全的内存操作能直接提升程序性能与稳定性,避免内存泄漏、越界访问等严重问题。应用场景广泛,包括动态数据结构构建、缓冲区处理、网络数据传输及嵌入式系统开发等。本文聚焦C标准库中的关键内存操作函数,如malloc、free、memcpy、memset和memmove,通过剖析其设计哲学与使用陷阱,

weixin_28739079的博客 303

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

C语言字符串函数模拟实现:从指针操作到内存安全的深度解析

在C语言编程中,字符串处理是基础且核心的技术环节,涉及字符数组、指针运算和内存管理等底层概念。其原理在于通过指针遍历内存中的字符序列,以空字符'\0'作为终止标识,实现拷贝、比较、连接等操作。掌握这些原理的技术价值在于,不仅能提升代码调试和性能优化能力,更是理解缓冲区溢出、内存安全等关键问题的基石。在应用场景上,从嵌入式系统开发到操作系统内核编写,健壮的字符串处理都是保障程序稳定性的关键。本文通过模拟实现strcpy、strncpy等标准库函数,深入探讨了指针操作、内存重叠处理及防御性编程,特别是strnc

weixin_34098209的博客 304

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

C语言内存操作函数实战:malloc、memcpy、memset深度解析与避坑指南

在C语言编程中,内存管理是核心基础,直接关系到程序的性能、稳定性和安全性。理解内存操作的底层原理,是掌握系统编程和高性能计算的关键。内存操作函数通过直接读写内存字节,实现了高效的数据分配、复制和初始化,其技术价值在于提供了对硬件资源的精细控制,避免了高级语言自动内存管理的开销。在嵌入式系统、操作系统开发、网络服务器等对性能和资源敏感的领域,这些函数是不可或缺的工具。本文聚焦于malloc、memcpy和memset等核心函数,结合内存泄漏和缓冲区溢出等常见问题,深入探讨其正确使用方法和性能优化技巧,帮助开发

weixin_42692935的博客 276

C语言进阶(5)——内存操作函数的解析

内存操作函数

zxj20041003的博客 458

c语言如何操作内存(数据类型、函数在内存中解析简介)

1、用变量名来访问内存 (c语言对内存地址的封装:数据类型、函数名) ---【直接访问内存(使用地址)】      数据类型:表示一个内存格子的长度和解析方法。(内存编址的单位是一个字节)      (int *) 0;  //0的地址存的是指针,指针指向一个int类型的数,数据类型int规定了内存格子长度:4字节    (float *) 0;  //0的地址存的是指针,指针指

what, when, why, how... 1262

深入解析C语言函数调用、内存管理与字符串操作:避坑指南+实战技巧!

if (strcmp("abc", "abcd") < 0) { /* 真:abc < abcd */ }if (strncmp("abc", "abcd", 3) == 0) { /* 真:前3字符相同 */ }| **数组传递**| 双向传递 | 退化为指针 | 数组操作 || **值传递** | 单向复制 | 栈空间独立 | 基础类型变量传递 || **地址传递**| 双向影响 | 间接修改原数据 | 结构体/指针传递 |// 手动添加结束符。

2403_90017956的博客 403

C语言内存操作函数深度解析:从memcpy到memmove的原理、陷阱与实战优化

内存操作是C语言编程的核心基础,它直接关系到程序的性能、安全性与稳定性。理解内存操作的底层原理,是掌握系统编程、嵌入式开发和高性能计算的关键。内存操作函数通过直接操作字节,实现了对任意数据类型的泛型处理,其技术价值在于提供了高效、灵活的数据搬运、初始化和比较能力。在应用场景上,从网络协议包的封装解析、自定义内存池的实现,到动态数组等数据结构的构建,都离不开这些函数的支撑。本文聚焦于memcpy、memmove、memset和memcmp等核心函数,深入剖析其行为差异、重叠内存处理、性能优化策略及常见陷阱,并

weixin_34238633的博客 499

C语言mem系列函数深度解析:从内存操作原理到高性能实现

在C语言编程中,内存操作是底层开发的核心基础。理解内存模型和字节流处理原理,是掌握高效、可靠系统编程的关键。mem系列函数(memcpy、memmove、memset、memcmp)作为直接操作原始内存块的底层工具,其价值在于能够处理任意数据类型,不依赖字符串终止符,通过明确的长度参数实现精确控制。这些函数在性能优化方面尤为重要,例如通过内存对齐、减少循环次数以及利用SIMD指令(如ARM NEON)进行向量化处理,可以大幅提升数据拷贝效率。在实际工程中,mem系列函数广泛应用于网络协议包处理、嵌入式系统寄

weixin_30983353的博客 250

C语言string.h函数深度解析:从内存操作到安全编程实践

在C语言编程中,字符串和内存操作是连接高级逻辑与底层硬件的核心桥梁。其原理基于对连续内存字节序列的直接读写,通过指针算术和字节拷贝实现高效的数据处理。掌握这些基础操作的技术价值在于,能够编写出既高效又安全的系统级代码,避免缓冲区溢出等严重安全漏洞。在实际应用场景中,无论是网络协议解析、文件系统操作还是嵌入式开发,都离不开对内存布局和字符串终止符的精准控制。本文聚焦于C标准库中的string.h函数集,深入剖析strcpy、strncpy、memcpy等关键函数的行为差异与隐藏陷阱,并结合strlcpy等热词

weixin_29197835的博客 290

C语言核心函数深度解析:strtok、memcpy等内存与字符串操作实战

在C语言程序开发中,内存与字符串操作是构建高效、稳定系统的基石。理解其底层原理,对于优化性能、避免隐蔽错误至关重要。内存操作函数如memcpy和memmove,直接与硬件内存模型交互,其性能差异源于对内存区域重叠与否的处理策略;而字符串函数如strtok,则涉及状态管理与线程安全等经典问题。这些函数的技术价值在于,它们既是实现数据序列化、解析、缓冲区管理等核心功能的关键,也是性能调优的重点对象,尤其在嵌入式、高性能服务器等场景。例如,结合ARM AArch64架构的NEON指令集进行向量化优化,可以显著提升

weixin_30437847的博客 447
上一篇: 【C++】【算法】高精度加减乘除
鼓浪屿Estelle
博客等级 码龄3年 307粉丝 11原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值