C语言内存函数

🎯前言

在C语言编程中,内存操作函数如 memcpymemmovememsetmemcmp 是处理内存数据的基础工具。它们提供了对内存块进行复制、移动、设置和比较的功能,使得程序能够高效地操作大块数据。然而,尽管这些函数看似简单,其底层实现却蕴含着许多巧妙的优化和细节。

本篇博客将详细介绍这些内存操作函数的使用方法,并通过模拟实现来剖析它们的内部机制。通过对比不同实现方式的效率和安全性,读者可以更好地理解这些函数的工作原理,避免在实际编程中可能遇到的陷阱和问题。

C语言内存函数

1.memcpy使用和模拟实现

1.1函数的使用

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

memcpy 的使用

下面是一个使用 memcpy 的示例:

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

int main() {
    char src[] = "Hello, World!";
    char dest[50];

    // 使用 memcpy 将 src 的内容复制到 dest
    memcpy(dest, src, strlen(src) + 1);  // +1 是为了包括 '\0' 终止符

    printf("Source: %s\n", src);
    printf("Destination: %s\n", dest);

    return 0;
}
memcpy 的注意事项
  1. 源和目标内存区域不能重叠memcpy 不保证在源和目标内存区域重叠时能够正确复制数据。如果需要处理重叠的内存区域,请使用 memmove 函数,它专门设计用于这种情况。

    // 错误示例:源和目标内存区域重叠
    char buffer[20] = "Hello, World!";
    memcpy(buffer + 6, buffer, 5);  // 不安全
    
  2. 确保目标内存足够大: 确保目标内存区域 dest 足够大,以容纳复制的字节数 n。否则,会导致缓冲区溢出和潜在的程序崩溃。

    char src[] = "Hello, World!";
    char dest[5];  // 缓冲区太小,无法容纳整个字符串
    
    memcpy(dest, src, strlen(src) + 1);  // 错误:缓冲区溢出
    
  3. 检查空指针: 在使用 memcpy 之前,确保源和目标指针不为 NULL。对空指针的操作会导致未定义行为。

    char *src = NULL;
    char dest[50];
    
    memcpy(dest, src, 10);  // 错误:src 是空指针
    
  4. 确保正确的字节数: 传递给 memcpy 的字节数 n 应该是准确的。如果传递的字节数大于源或目标内存区域的大小,会导致读取或写入越界。

    char src[] = "Hello";
    char dest[50];
    
    memcpy(dest, src, 100);  // 错误:读取越界
    

参数size_t n:

下面将通过一个对整型数组的拷贝案列让你对该参数有个更深的理解

#include <stdio.h>
#include <string.h>
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8,9 };
	int arr2[10] = {0,0};

	memcpy(arr2+1, arr1, 4 * sizeof(int));

	int i = 0;
	for (i = 0; i < 4; i++)
	{
		printf("%d ", arr2[i]);
	}

	return 0;
}
  1. memcpy(arr2+1, arr1, 4 * sizeof(int));我们可以看书我们传参的是4*sizeof(int),穿的是字节,该函数是以字节方式进行一个字节一个字节的拷贝
  2. memcpy(arr2+1, arr1, 4 * sizeof(int));arr2+1表示的是从数组第二个元素位置进行拷贝

输出结果 0 1 2 3

总结

1.2函数的模拟实现

memcpy 是一个强大且高效的函数,用于内存复制操作。为了正确使用它,请务必确保源和目标内存区域不重叠、目标内存足够大、指针不为空,并传递准确的字节数。通过遵循这些注意事项,可以有效避免常见的内存管理问题,提高程序的稳定性和可靠性。

#include <stdio.h>

// 自定义的 memcpy 实现
void *my_memcpy(void *dest, const void *src, size_t n) {
    // 将输入的 void 指针转换为 char 指针
    char *d = (char *)dest;
    const char *s = (const char *)src;

    // 逐字节复制数据
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }

    // 返回目标指针
    return dest;
}

int main() {
    // 定义并初始化源数组
    int src[] = {1, 2, 3, 4, 5};
    // 目标数组大小应该至少与源数组一样大
    int dest[sizeof(src) / sizeof(src[0])];

    // 计算要复制的字节数
    size_t n = sizeof(src);

    // 使用自定义的 my_memcpy 将 src 数组内容复制到 dest 数组
    my_memcpy(dest, src, n);

    // 打印目标数组以验证复制结果
    printf("Destination array: ");
    for (size_t i = 0; i < sizeof(dest) / sizeof(dest[0]); i++) {
        printf("%d ", dest[i]);
    }
    printf("\n");

    return 0;
}
代码解释
  1. 定义 my_memcpy 函数

    • void *my_memcpy(void *dest, const void *src, size_t n) 是自定义的 memcpy 函数,接受目标指针 dest、源指针 src 和要复制的字节数 n
  2. 指针类型转换

    • void 类型的指针 destsrc 转换为 char 类型指针,以便进行逐字节复制。
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
  3. 逐字节复制数据

    • 使用 for 循环遍历 n 个字节,将源指针 s 指向的内存内容复制到目标指针 d
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }
    
  4. 返回目标指针

    • my_memcpy 函数返回目标指针 dest,与标准 memcpy 函数的行为一致。
    return dest;
    

2.memmove使用和模拟实现

2.1函数的使用

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

### memmove 的使用

以下是一个使用 memmove 的示例,演示如何在数组中移动数据:

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

int main() {
    char str[] = "Hello, World!";
    
    // 使用 memmove 将 "World" 移动到字符串的起始位置
    memmove(str, str + 7, 6);  // 包括 '\0' 终止符

    printf("Modified string: %s\n", str);

    return 0;
}
memmove 的注意事项
  1. 源和目标内存区域可以重叠memmove 的主要优势在于它能够正确处理源和目标内存区域重叠的情况。在这种情况下,它会先将数据复制到一个临时缓冲区,然后再将数据从临时缓冲区复制到目标区域,以避免数据被覆盖。

    // 示例:源和目标内存区域重叠
    char buffer[20] = "Hello, World!";
    memmove(buffer + 6, buffer, 5);  // 安全
    buffer[11] = '\0';  // 添加终止符
    printf("Buffer: %s\n", buffer);  // 输出 "Hello Hello"
    
  2. 确保目标内存足够大: 确保目标内存区域 dest 足够大,以容纳要移动的字节数 n。否则,会导致缓冲区溢出和潜在的程序崩溃。

    char src[] = "Hello, World!";
    char dest[5];  // 缓冲区太小,无法容纳整个字符串
    
    memmove(dest, src, strlen(src) + 1);  // 错误:缓冲区溢出
    
  3. 检查空指针: 在使用 memmove 之前,确保源和目标指针不为 NULL。对空指针的操作会导致未定义行为。

    char *src = NULL;
    char dest[50];
    
    memmove(dest, src, 10);  // 错误:src 是空指针
    
  4. 确保正确的字节数: 传递给 memmove 的字节数 n 应该是准确的。如果传递的字节数大于源或目标内存区域的大小,会导致读取或写入越界。

    char src[] = "Hello";
    char dest[50];
    
    memmove(dest, src, 100);  // 错误:读取越界
    

2.2函数的模拟实现

#include <stdio.h>

// 自定义的 memmove 实现
void *my_memmove(void *dest, const void *src, size_t n) {
    // 将输入的 void 指针转换为 char 指针
    char *d = (char *)dest;
    const char *s = (const char *)src;

    if (d < s) {
        // 如果目标地址在源地址之前,从前往后复制
        for (size_t i = 0; i < n; i++) {
            d[i] = s[i];
        }
    } else {
        // 如果目标地址在源地址之后,从后往前复制
        for (size_t i = n; i != 0; i--) {
            d[i - 1] = s[i - 1];
        }
    }

    // 返回目标指针
    return dest;
}

int main() {
    char str[] = "Hello, World!";
    
    // 使用 my_memmove 将 "World" 移动到字符串的起始位置
    my_memmove(str, str + 7, 6);  // 包括 '\0' 终止符

    printf("Modified string: %s\n", str);

    return 0;
}
代码解释
  1. 定义 my_memmove 函数

    • void *my_memmove(void *dest, const void *src, size_t n) 是自定义的 memmove 函数,接受目标指针 dest、源指针 src 和要复制的字节数 n
  2. 指针类型转换

    • void 类型的指针 destsrc 转换为 char 类型指针,以便进行逐字节复制。
    char *d = (char *)dest;
    const char *s = (const char *)src;
    
  3. 处理内存区域重叠

    • 判断目标地址是否在源地址之前。如果是,从前往后逐字节复制数据;否则,从后往前逐字节复制数据。这是为了确保在源和目标内存区域重叠时不会覆盖未复制的数据。

在这里插入图片描述

```c
if (d < s) {
    for (size_t i = 0; i < n; i++) {
        d[i] = s[i];
    }
} else {
    for (size_t i = n; i != 0; i--) {
        d[i - 1] = s[i - 1];
    }
}
```
  1. 返回目标指针

    • my_memmove 函数返回目标指针 dest,与标准 memmove 函数的行为一致。
    return dest;
    

3.memset函数的使用

3.1函数的使用

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

memset 的使用

以下是一个使用 memset 的示例,演示如何将数组初始化为特定值:

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

int main() {
    char buffer[50];

    // 使用 memset 将 buffer 的前 10 个字节设置为 'A'
    memset(buffer, 'A', 10);

    // 确保字符串以 '\0' 终止符结尾
    buffer[10] = '\0';

    printf("Buffer: %s\n", buffer);

    return 0;
}

### memset 的注意事项

  1. 参数类型和含义

    • void *s: 指向要填充的内存区域的指针。
    • int c: 要设置的值,会被转换为 unsigned char 后填充到内存块中。
    • size_t n: 要填充的字节数。
  2. 确保内存区域大小: 确保要填充的内存区域至少有 n 个字节,否则会导致内存访问越界,从而引发未定义行为。

    char buffer[5];
    memset(buffer, 'A', 10);  // 错误:buffer 只有 5 个字节
    
  3. 设置正确的值: 由于 int c 会被转换为 unsigned char,因此只会使用 c 的最低 8 位。确保传递给 c 的值在 0255 之间。

    int c = 300;  // 超出范围
    char buffer[10];
    memset(buffer, c, sizeof(buffer));  // 300 被截断为 44 (300 % 256)
    
  4. 使用正确的字节数: 传递给 memset 的字节数 n 应该是准确的。如果传递的字节数大于内存区域的大小,会导致缓冲区溢出。

    char buffer[10];
    memset(buffer, 'A', 20);  // 错误:buffer 只有 10 个字节
    
  5. 应用场景memset 通常用于初始化数组或结构体的内存,将内存区域重置为 0 或其他特定值。

    struct MyStruct {
        int a;
        double b;
        char c[10];
    };
    
    struct MyStruct s;
    memset(&s, 0, sizeof(s));  // 将 s 的所有字节设置为 0
    

4.memcmp函数的使用

4.1函数的使用

memcmp 是 C 语言标准库中的一个函数,用于比较两个内存块的内容。它的函数原型如下:

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

memcmp 的使用

以下是一个使用 memcmp 的示例,演示如何比较两个内存块:

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

int main() {
    char buffer1[] = "Hello, World!";
    char buffer2[] = "Hello, world!";  // 注意:这里的 'w' 是小写

    // 比较前 13 个字节
    int result = memcmp(buffer1, buffer2, 13);

    if (result == 0) {
        printf("The buffers are equal.\n");
    } else if (result < 0) {
        printf("buffer1 is less than buffer2.\n");
    } else {
        printf("buffer1 is greater than buffer2.\n");
    }

    return 0;
}

memcmp 的注意事项

  1. 参数类型和含义

    • const void *s1: 指向第一个要比较的内存块的指针。
    • const void *s2: 指向第二个要比较的内存块的指针。
    • size_t n: 要比较的字节数。
  2. 比较结果

    • 如果返回值为 0,则表示两个内存块在前 n 个字节内相等。
    • 如果返回值小于 0,则表示在第一个不同的字节处,s1 中的字节小于 s2 中的字节。
    • 如果返回值大于 0,则表示在第一个不同的字节处,s1 中的字节大于 s2 中的字节。
  3. 内存块的大小: 确保要比较的内存块至少有 n 个字节,否则会导致内存访问越界,从而引发未定义行为。

    char buffer1[5] = "Hello";
    char buffer2[5] = "Hello";
    int result = memcmp(buffer1, buffer2, 10);  // 错误:缓冲区只有 5 个字节
    
  4. 比较二进制数据memcmp 可以用于比较任何类型的二进制数据,包括结构体、数组等。

    struct MyStruct {
        int a;
        double b;
        char c[10];
    };
    
    struct MyStruct s1 = {1, 2.0, "hello"};
    struct MyStruct s2 = {1, 2.0, "hello"};
    
    int result = memcmp(&s1, &s2, sizeof(struct MyStruct));
    
  5. 避免字符串比较问题memcmp 比较的是二进制数据,因此在比较字符串时,不会忽略大小写或终止符。与字符串比较函数不同,memcmp 会比较整个内存块。

    char str1[] = "Hello";
    char str2[] = "hello";
    
    int result = memcmp(str1, str2, strlen(str1));  // 注意:区分大小写
    
总结

memcmp 是一个用于比较两个内存块的有用工具。在使用 memcmp 时,请确保内存块的大小足够大、传递正确的字节数,并理解返回值的含义。通过正确使用 memcmp,可以有效地比较任意类型的二进制数据,提高程序的可靠性和稳定性。


🥇结语

通过本篇博客,我们深入探讨了C语言中 memcpymemmovememsetmemcmp 等内存操作函数的使用方法和内部实现。理解这些函数的底层机制不仅有助于提高编程效率,还能帮助我们避免许多常见的内存管理问题。

在现代编程中,内存操作的效率和安全性至关重要。通过正确地使用这些内存函数,我们可以编写出更高效、更可靠的代码。同时,通过模拟实现这些函数,我们能够更好地理解其背后的原理,从而在需要时能够自行实现或优化类似的功能。

希望本篇博客能为你在C语言编程中的内存操作提供有益的指导和启发。如果你有任何问题或建议,欢迎在评论区分享你的想法。感谢阅读,祝你在编程的道路上不断进步!

  • 46
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值