📖 目录


📚 1. 引言

大家好,欢迎来到C语言内存函数的专题讲解。在C语言编程中,内存操作是极其重要的一环,尤其是在嵌入式开发、系统编程和面试中,memcpymemmovememsetmemcmp 这四个内存函数几乎是必考知识点。

本文将带你从函数原型使用示例模拟实现面试常见问题,全方位掌握这些内存函数。文章内容基于经典教材和实际开发经验,代码完整可运行,非常适合面试前的系统复习。

📦 2. memcpy 函数:内存拷贝的基础

2.1 函数原型

void * memcpy ( void * destination, const void * source, size_t num );

2.2 功能说明

  • memcpysource 位置开始,向后复制 num 个字节的数据到 destination 指向的内存位置。
  • 这个函数在遇到 '\0' 的时候并不会停下来,它只关心 num 指定的字节数。
  • ⚠️ 重要限制:如果 sourcedestination 有任何的重叠,复制的结果都是未定义的。对于重叠的内存,应该交给 memmove 来处理。

2.3 使用示例

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

int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[10] = { 0 };
    
    // 将 arr1 的前 20 个字节(5个int)复制到 arr2
    memcpy(arr2, arr1, 20);
    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr2[i]);
    }
    // 输出结果:1 2 3 4 5 0 0 0 0 0
    
    return 0;
}

2.4 模拟实现

void * memcpy ( void * dst, const void * src, size_t count)
{
    void * ret = dst;
    assert(dst);
    assert(src);
    
    /*
     * copy from lower addresses to higher addresses
     */
    while (count--) {
        *(char *)dst = *(char *)src;
        dst = (char *)dst + 1;
        src = (char *)src + 1;
    }
    return(ret);
}

💡 实现要点

  • 使用 char * 指针逐字节拷贝,因为 char 类型占1个字节,可以精确控制拷贝的字节数。
  • 先保存 dst 的原始地址作为返回值,方便链式调用。
  • 使用 assert 进行空指针检查,增强代码健壮性。

🔄 3. memmove 函数:处理重叠内存的拷贝

3.1 函数原型

void * memmove ( void * destination, const void * source, size_t num );

3.2 功能说明

  • memcpy 的差别就是 memmove 函数处理的源内存块和目标内存块是可以重叠的。
  • 如果源空间和目标空间出现重叠,就得使用 memmove 函数处理。

3.3 使用示例

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

int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    
    // 将 arr1 的前 20 个字节复制到 arr1+2 的位置(重叠场景)
    memmove(arr1+2, arr1, 20);
    
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("%d ", arr1[i]);
    }
    // 输出结果:1 2 1 2 3 4 5 8 9 10
    
    return 0;
}

输出结果:

1 2 1 2 3 4 5 8 9 10

3.4 模拟实现

void * memmove ( void * dst, const void * src, size_t count)
{
    void * ret = dst;
    
    if (dst <= src || (char *)dst >= ((char *)src + count)) {
        /*
         * Non-Overlapping Buffers
         * copy from lower addresses to higher addresses
         */
        while (count--) {
            *(char *)dst = *(char *)src;
            dst = (char *)dst + 1;
            src = (char *)src + 1;
        }
    }
    else {
        /*
         * Overlapping Buffers
         * copy from higher addresses to lower addresses
         */
        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);
}

💡 实现要点

  • 核心思想是判断内存是否重叠,以及从哪个方向开始拷贝
  • dst <= srcdst >= src + count 时,说明没有重叠或目标地址在源地址之前,可以从低地址向高地址拷贝。
  • 当存在重叠且目标地址在源地址之后时,必须从高地址向低地址拷贝,避免覆盖未拷贝的数据。

🧹 4. memset 函数:内存设置

4.1 函数原型

void * memset ( void * ptr, int value, size_t num );

4.2 功能说明

memset 是用来设置内存的,将内存中的值以字节为单位设置成想要的内容。

4.3 使用示例

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

int main ()
{
    char str[] = "hello world";
    memset (str,'x',6);
    printf(str);
    // 输出结果:xxxxxxworld
    
    return 0;
}

输出结果:

xxxxxxworld

💡 注意事项

  • memset字节为单位操作,对于非字符数组(如 int 数组),设置的值会被解释为字节值。
  • 例如 memset(arr, 1, 20) 会将每个字节设为 0x01,而不是将每个 int 设为 1
  • 常用于初始化数组为0memset(arr, 0, sizeof(arr))

⚖️ 5. memcmp 函数:内存比较

5.1 函数原型

int memcmp ( const void * ptr1, const void * ptr2, size_t num );

5.2 功能说明

  • 比较从 ptr1ptr2 指针指向的位置开始,向后的 num 个字节。
  • 返回值
    • 返回值 < 0ptr1 小于 ptr2
    • 返回值 = 0ptr1 等于 ptr2
    • 返回值 > 0ptr1 大于 ptr2

5.3 使用示例

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

int main()
{
    char buffer1[] = "DWgaOtP12df0";
    char buffer2[] = "DWGAOTP12DF0";
    int n;
    
    n = memcmp(buffer1, buffer2, sizeof(buffer1));
    
    if (n > 0) 
        printf("'%s' is greater than '%s'.\n", buffer1, buffer2);
    else if (n < 0) 
        printf("'%s' is less than '%s'.\n", buffer1, buffer2);
    else 
        printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
    
    return 0;
}

💡 strcmp 的区别

  • strcmp 遇到 '\0' 就停止比较,而 memcmp 比较指定的 num 个字节。
  • memcmp 可以比较任意内存块(包括包含 '\0' 的数据),而 strcmp 只能比较字符串。

📝 6. 总结

函数 功能 处理重叠内存 停止条件
memcpy 内存拷贝 ❌ 未定义 指定字节数
memmove 内存拷贝 ✅ 安全处理 指定字节数
memset 内存设置 指定字节数
memcmp 内存比较 指定字节数

核心记忆口诀

  • memcpy:不重叠,直接拷
  • memmove:重叠了,分方向
  • memset:设内存,按字节
  • memcmp:比内存,定字节

🎯 7. 经典面试题

面试题 1:memcpymemmove 有什么区别?什么时候用 memmove

解答

  • memcpy 不保证能正确处理源和目标内存重叠的情况,如果内存重叠,结果是未定义的。
  • memmove 可以安全处理重叠内存。
  • 当源内存块和目标内存块有重叠时,必须使用 memmove;否则两者都可以使用,但 memcpy 通常效率略高。

面试题 2:memcpystrcpy 有什么区别?

解答

  • strcpy 只能用于字符串拷贝,遇到 '\0' 停止;memcpy 可以拷贝任意类型的内存数据。
  • strcpy 不指定拷贝长度;memcpy 需要指定拷贝的字节数。
  • memcpy 可以拷贝包含 '\0' 的数据块,而 strcpy 遇到 '\0' 就停止。

面试题 3:memset(arr, 1, sizeof(arr)) 能把 int 数组的每个元素设为 1 吗?

解答

  • 不能memset 以字节为单位设置内存,int 占4个字节,每个字节都会被设为 0x01,所以每个 int 的值实际上是 0x01010101(十六进制),即 16843009
  • 正确做法是使用循环或 memset(arr, 0, sizeof(arr)) 来清零数组。

面试题 4:请手写一个 memmove 的实现,并说明为什么需要分方向拷贝?

解答

  • 参考上文第3.4节的模拟实现。
  • 分方向拷贝的原因:当源地址和目标地址重叠,且目标地址在源地址之后时,如果从低地址向高地址拷贝,会先覆盖源数据中尚未拷贝的部分,导致数据错误。从高地址向低地址拷贝可以避免这个问题。

面试题 5:memcmpstrcmp 的返回值规则一样吗?比较结果有什么不同?

解答

  • 返回值规则相同:都返回 >0=0<0
  • 不同点:strcmp 遇到 '\0' 停止比较,而 memcmp 比较完指定的 num 个字节才停止。因此对于包含 '\0' 的二进制数据,只能用 memcmp 进行比较。

🚀 面试小贴士:面试官通常会让手写 memcpymemmove 的模拟实现,重点考察对内存重叠的处理、指针操作和边界条件的把握。建议在面试前多练习几遍手写代码,做到烂熟于心。

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐