C语言内存函数

文章目录

🎯前言

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

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

C语言内存函数

1.memcpy使用和模拟实现

1.1函数的使用

c 复制代码
void *memcpy(void *dest, const void *src, size_t n);

memcpy 的使用

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

c 复制代码
#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 函数,它专门设计用于这种情况。

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

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

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

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

参数size_t n:

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

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

c 复制代码
#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 类型指针,以便进行逐字节复制。
    c 复制代码
    char *d = (char *)dest;
    const char *s = (const char *)src;
  3. 逐字节复制数据

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

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

2.memmove使用和模拟实现

2.1函数的使用

c 复制代码
void *memmove(void *dest, const void *src, size_t n);

### memmove 的使用

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

c 复制代码
#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 的主要优势在于它能够正确处理源和目标内存区域重叠的情况。在这种情况下,它会先将数据复制到一个临时缓冲区,然后再将数据从临时缓冲区复制到目标区域,以避免数据被覆盖。

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

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

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

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

2.2函数的模拟实现

c 复制代码
#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 类型指针,以便进行逐字节复制。
    c 复制代码
    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 函数的行为一致。
    c 复制代码
    return dest;

3.memset函数的使用

3.1函数的使用

c 复制代码
void *memset(void *s, int c, size_t n);

memset 的使用

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

c 复制代码
#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 个字节,否则会导致内存访问越界,从而引发未定义行为。

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

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

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

    c 复制代码
    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 语言标准库中的一个函数,用于比较两个内存块的内容。它的函数原型如下:

c 复制代码
int memcmp(const void *s1, const void *s2, size_t n);

memcmp 的使用

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

c 复制代码
#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 个字节,否则会导致内存访问越界,从而引发未定义行为。

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

    c 复制代码
    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 会比较整个内存块。

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

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


🥇结语

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

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

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

相关推荐
Lizhihao_9 分钟前
JAVA-队列
java·开发语言
远望清一色27 分钟前
基于MATLAB边缘检测博文
开发语言·算法·matlab
何曾参静谧35 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices39 分钟前
C++如何调用Python脚本
开发语言·c++·python
我狠狠地刷刷刷刷刷1 小时前
中文分词模拟器
开发语言·python·算法
wyh要好好学习1 小时前
C# WPF 记录DataGrid的表头顺序,下次打开界面时应用到表格中
开发语言·c#·wpf
AitTech1 小时前
C#实现:电脑系统信息的全面获取与监控
开发语言·c#
qing_0406031 小时前
C++——多态
开发语言·c++·多态
孙同学_1 小时前
【C++】—掌握STL vector 类:“Vector简介:动态数组的高效应用”
开发语言·c++
froginwe111 小时前
XML 编辑器:功能、选择与使用技巧
开发语言