C语言内存函数

1. memcpy 函数:内存块拷贝(不重叠)

memcpy 是 C 语言中用于内存拷贝的标准库函数,它不关心内存中存放的是什么类型的数据,只按字节进行复制。

c 复制代码
void * memcpy ( void * destination, const void * source, size_t num );

功能

  • source 指向的位置开始,向后复制 num 个字节的数据到 destination 指向的内存位置。
  • 重要限制 :如果 sourcedestination 指向的内存区域有任何重叠,复制结果是未定义的。
  • memcpy 只负责拷贝不重叠 的内存;若存在重叠,应使用 memmove
  • 使用前需要包含头文件 <string.h>

参数说明

参数 含义
destination 目标空间指针,拷贝的数据存放于此
source 源空间指针,要拷贝的数据来源于此
num 要拷贝的字节数

返回值

拷贝完成后,返回目标空间的起始地址(即 destination 的值)。

1.1 代码演示

c 复制代码
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[20] = { 0 };
    int sz2 = sizeof(arr2) / sizeof(arr2[0]);
    memcpy(arr2, arr1+2, 20);
    for (int i = 0; i < sz2; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

代码逐行解释

  • int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };:定义一个整型数组 arr1,包含 10 个元素。
  • int arr2[20] = { 0 };:定义一个 20 个元素的整型数组 arr2,全部初始化为 0。
  • int sz2 = sizeof(arr2) / sizeof(arr2[0]);:计算 arr2 的元素个数(20)。
  • memcpy(arr2, arr1+2, 20);:从 arr1 的第 3 个元素(arr1+2,即 3)开始,拷贝 20 个字节(即 5 个 int,因为 sizeof(int)=4)到 arr2 的起始位置。拷贝的内容为 {3,4,5,6,7}
  • 循环打印 arr2 的前 20 个元素,结果为 3 4 5 6 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

1.2 模拟实现

c 复制代码
#include <assert.h>

void* my_memcpy(void* dest, const void* src, size_t num)
{
    void* ret = dest;          // 保存目标起始地址,用于返回
    assert(dest && src);       // 断言:dest 和 src 都不能为空指针

    while (num--)              // 逐字节拷贝,拷贝 num 次
    {
        *(char*)dest = *(char*)src;   // 将 src 当前字节拷贝到 dest
        dest = (char*)dest + 1;       // dest 指针后移 1 字节
        src = (char*)src + 1;         // src 指针后移 1 字节
    }
    return ret;                // 返回目标起始地址
}

代码逐行解释

  • void* my_memcpy(void* dest, const void* src, size_t num):使用 void* 接收任意类型的指针,const 修饰 src 表示不修改源数据。
  • void* ret = dest;:保存 dest 的原始地址,因为后面 dest 会移动。
  • assert(dest && src);:断言检查,确保两个指针都不为 NULL,若为 NULL 则程序终止。
  • while (num--):循环 num 次,每次拷贝一个字节。num-- 先判断 num 是否为非 0,再自减。
  • *(char*)dest = *(char*)src;:将 destsrc 强制转换为 char*(因为 char 占 1 字节),然后解引用赋值,完成一个字节的拷贝。
  • dest = (char*)dest + 1;src = (char*)src + 1;:将指针向后移动 1 个字节,指向下一个待拷贝的位置。
  • return ret;:返回最初保存的目标起始地址,支持链式调用。

测试代码

c 复制代码
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int arr2[20] = { 0 };
    int sz2 = sizeof(arr2) / sizeof(arr2[0]);
    my_memcpy(arr2, arr1+2, 20);
    for (int i = 0; i < sz2; i++)
    {
        printf("%d ", arr2[i]);
    }
    return 0;
}

输出结果与标准 memcpy 一致:3 4 5 6 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0


2. memmove 函数:内存块拷贝(支持重叠)

c 复制代码
void * memmove ( void * destination, const void * source, size_t num );

功能

  • memmove 也用于内存块拷贝,与 memcpy核心区别 在于:memmove 可以正确处理源内存块和目标内存块重叠的情况。
  • 使用前需要包含头文件 <string.h>

参数说明

参数 含义
destination 目标空间指针
source 源空间指针
num 要拷贝的字节数

返回值

拷贝完成后,返回目标空间的起始地址。

2.1 代码演示

c 复制代码
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz1 = sizeof(arr1) / sizeof(arr1[0]);
    memmove(arr1 + 2, arr1, 20);
    for (int i = 0; i < sz1; i++)
    {
        printf("%d ", arr1[i]);
    }
    return 0;
}

代码逐行解释

  • int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };:定义数组 arr1
  • int sz1 = sizeof(arr1) / sizeof(arr1[0]);:计算元素个数(10)。
  • memmove(arr1 + 2, arr1, 20);:将 arr1 的前 20 个字节(即前 5 个 int1,2,3,4,5)拷贝到 arr1+2 开始的位置(即第 3 个元素开始)。这里源和目标有重叠(源是 arr1[0]~arr1[4],目标是 arr1[2]~arr1[6]),memmove 能正确处理。
  • 循环打印结果:1 2 1 2 3 4 5 8 9 10。可以看到,arr1[2]arr1[3] 被正确复制为原来的 12,而不是被覆盖后的错误值。

2.2 模拟实现

c 复制代码
#include <assert.h>

void* my_memmove(void* dest, const void* src, size_t num)
{
    void* ret = dest;
    assert(dest && src);

    if (dest < src)  // 目标地址在源地址之前,从前往后拷贝
    {
        while (num--)
        {
            *(char*)dest = *(char*)src;
            dest = (char*)dest + 1;
            src = (char*)src + 1;
        }
    }
    else  // 目标地址在源地址之后(或相等),从后往前拷贝
    {
        while (num--)
        {
            *((char*)dest + num) = *((char*)src + num);
        }
    }
    return ret;
}

代码逐行解释

  • if (dest < src):判断目标地址是否小于源地址。如果是,说明目标在源的前面,从前往后拷贝不会覆盖未拷贝的源数据。
    • 从前往后拷贝的逻辑与 my_memcpy 相同。
  • else:目标地址在源地址之后(或相等),此时如果从前往后拷贝,会先覆盖源中尚未拷贝的数据。因此需要从后往前 拷贝。
    • while (num--):循环 num 次。
    • *((char*)dest + num) = *((char*)src + num);:每次拷贝最后一个字节。(char*)dest + num 指向目标区域的最后一个字节,(char*)src + num 指向源区域的最后一个字节。随着 num 递减,依次向前拷贝,确保不会覆盖未拷贝的数据。

测试代码

c 复制代码
int main()
{
    int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
    int sz1 = sizeof(arr1) / sizeof(arr1[0]);
    my_memmove(arr1 + 2, arr1, 20);
    for (int i = 0; i < sz1; i++)
    {
        printf("%d ", arr1[i]);
    }
    return 0;
}

输出结果:1 2 1 2 3 4 5 8 9 10,与标准 memmove 一致。


3. memset 函数:内存设置

c 复制代码
void * memset ( void * ptr, int value, size_t num );

功能

ptr 指向的内存块的前 num 个字节设置为 value(以 unsigned char 方式转换),常用于初始化数组或结构体。

  • 头文件<string.h>
  • 参数
    • ptr:指向要设置的内存空间起始地址
    • value:要设置的值(以 unsigned char 为单位转换)
    • num:要设置的字节数
  • 返回值 :返回 ptr 的起始地址

使用示例

c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
    memset(arr, 0, sizeof(arr));  // 将 arr 所有字节设为 0
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);    // 输出:0 0 0 0 0
    }
    return 0;
}

注意memset 按字节设置,对于非 char 类型的数组,设置非 0 值时要小心。例如 memset(arr, 1, sizeof(arr)) 会将每个字节设为 0x01,对于 int 数组,每个元素会变成 0x01010101(即 16843009),而不是 1。

3.1 常见陷阱与正确用法

陷阱:对 int 数组设置非 0 值

memset 是按字节 进行设置的,而 int 类型通常占 4 个字节。当使用 memsetint 数组设置非 0 值时,会将每个字节都设置为该值,导致结果与预期不符。

c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr[5];
    memset(arr, 1, sizeof(arr));  // 将每个字节设为 0x01
    for (int i = 0; i < 5; i++)
    {
        printf("arr[%d] = %d (0x%08x)\n", i, arr[i], arr[i]);
    }
    return 0;
}

输出结果

复制代码
arr[0] = 16843009 (0x01010101)
arr[1] = 16843009 (0x01010101)
arr[2] = 16843009 (0x01010101)
arr[3] = 16843009 (0x01010101)
arr[4] = 16843009 (0x01010101)

原理分析

  • memset(arr, 1, sizeof(arr))arr 所占的 20 个字节(5 个 int × 4 字节)每个字节都写为 0x01
  • 对于小端字节序的机器,每个 int 在内存中的 4 个字节为 01 01 01 01,对应的整数值为 0x01010101,即十进制的 16843009。
  • 因此,我们期望的 arr[i] = 1 并没有实现,而是得到了一个很大的数。

正确用法:使用循环初始化

如果需要将 int 数组初始化为特定的非 0 值(如 1、-1 等),应使用循环逐个元素赋值。

c 复制代码
#include <stdio.h>

int main()
{
    int arr[5];
    for (int i = 0; i < 5; i++)
    {
        arr[i] = 1;  // 正确:每个元素赋值为 1
    }
    for (int i = 0; i < 5; i++)
    {
        printf("arr[%d] = %d\n", i, arr[i]);
    }
    return 0;
}

输出结果

复制代码
arr[0] = 1
arr[1] = 1
arr[2] = 1
arr[3] = 1
arr[4] = 1

特殊情况:初始化为 0 或 -1

虽然 memset 对非 0 值有陷阱,但对于 0-1 这两个特殊值,memset 可以正确工作:

  • 初始化为 0 :每个字节设为 0x00int 值为 0x00000000,即 0。
  • 初始化为 -1 :每个字节设为 0xFFint 值为 0xFFFFFFFF,即 -1(补码表示)。
c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr1[5];
    int arr2[5];

    memset(arr1, 0, sizeof(arr1));   // 每个字节 0x00 → int 值为 0
    memset(arr2, 0xFF, sizeof(arr2)); // 每个字节 0xFF → int 值为 -1

    for (int i = 0; i < 5; i++)
    {
        printf("arr1[%d] = %d, arr2[%d] = %d\n", i, arr1[i], i, arr2[i]);
    }
    return 0;
}

输出结果

复制代码
arr1[0] = 0, arr2[0] = -1
arr1[1] = 0, arr2[1] = -1
arr1[2] = 0, arr2[2] = -1
arr1[3] = 0, arr2[3] = -1
arr1[4] = 0, arr2[4] = -1

正确用法总结表

目标值 推荐方式 说明
0 memset(arr, 0, sizeof(arr)) 每个字节为 0x00,结果为 0
-1 memset(arr, 0xFF, sizeof(arr)) 每个字节为 0xFF,结果为 -1
其他值(如 1、100) 循环赋值 for (int i = 0; i < n; i++) arr[i] = value;

3.2 模拟实现

c 复制代码
#include <assert.h>

void* my_memset(void* ptr, int value, size_t num)
{
    void* ret = ptr;                // 保存起始地址,用于返回
    assert(ptr != NULL);            // 断言:ptr 不能为空指针

    unsigned char val = (unsigned char)value;  // 将 value 转换为 unsigned char
    unsigned char* p = (unsigned char*)ptr;    // 将 void* 转换为 unsigned char*

    for (size_t i = 0; i < num; i++)
    {
        p[i] = val;                 // 逐字节设置
    }
    return ret;                     // 返回起始地址
}

代码逐行解释

  • void* my_memset(void* ptr, int value, size_t num):使用 void* 接收任意类型的指针,int value 与标准 memset 一致,size_t num 表示要设置的字节数。
  • void* ret = ptr;:保存 ptr 的原始地址,用于最后返回。
  • assert(ptr != NULL);:断言检查,确保指针不为 NULL,若为 NULL 则程序终止。
  • unsigned char val = (unsigned char)value;:将 value 强制转换为 unsigned char。这是关键步骤,因为 memset 实际上是以 unsigned char 为单位来设置内存的,value 的高位字节会被截断,只保留低 8 位。
  • unsigned char* p = (unsigned char*)ptr;:将 void* 转换为 unsigned char*,因为 unsigned char 占 1 字节,可以逐字节操作内存。
  • for (size_t i = 0; i < num; i++):循环 num 次,逐字节设置。
    • p[i] = val;:将当前字节设置为 val
  • return ret;:返回最初保存的起始地址,支持链式调用。

测试代码

c 复制代码
#include <stdio.h>

int main()
{
    int arr[5] = {1, 2, 3, 4, 5};

    // 测试1:将数组清零
    my_memset(arr, 0, sizeof(arr));
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);  // 输出:0 0 0 0 0
    }
    printf("\n");

    // 测试2:将数组每个字节设为 0xFF(即 -1)
    my_memset(arr, 0xFF, sizeof(arr));
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);  // 输出:-1 -1 -1 -1 -1
    }
    printf("\n");

    // 测试3:将字符数组设置为 'A'
    char str[10];
    my_memset(str, 'A', 9);
    str[9] = '\0';
    printf("%s\n", str);  // 输出:AAAAAAAAA

    return 0;
}

输出结果

复制代码
0 0 0 0 0
-1 -1 -1 -1 -1
AAAAAAAAA

关键点总结

  • my_memset字节逐位设置,不关心数据类型。
  • 使用 unsigned char* 进行逐字节操作,确保行为与标准 memset 一致。
  • value 会被截断为低 8 位(unsigned char 范围),因此设置非 0 值时要理解其字节级效果。
  • 返回原始指针地址,支持链式调用(如 memset(memset(arr, 0, 4), 1, 4))。

4. memcmp 函数:内存比较

c 复制代码
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

功能

比较 ptr1ptr2 指向的内存块的前 num 个字节。

  • 头文件<string.h>
  • 参数
    • ptr1:指向第一块待比较的内存
    • ptr2:指向第二块待比较的内存
    • num:要比较的字节数
  • 返回值
    • < 0ptr1 小于 ptr2
    • = 0:相等
    • > 0ptr1 大于 ptr2

使用示例

c 复制代码
#include <stdio.h>
#include <string.h>

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {1, 2, 3, 4, 6};
    int result = memcmp(arr1, arr2, sizeof(arr1));
    if (result == 0)
        printf("两个数组相等\n");
    else if (result < 0)
        printf("arr1 小于 arr2\n");
    else
        printf("arr1 大于 arr2\n");
    return 0;
}

输出arr1 小于 arr2(因为最后一个元素 5 < 6)。

4.1 模拟实现

c 复制代码
#include <assert.h>

int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
    assert(ptr1 && ptr2);  // 断言:两个指针都不能为空

    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 my_memcmp(const void* ptr1, const void* ptr2, size_t num):使用 const void* 接收任意类型的指针,const 保证不修改指针指向的数据。返回值类型为 int,与标准 memcmp 一致。
  • assert(ptr1 && ptr2);:断言检查,确保两个指针都不为 NULL,若为 NULL 则程序终止。
  • const unsigned char* p1 = (const unsigned char*)ptr1;const unsigned char* p2 = (const unsigned char*)ptr2;:将 void* 强制转换为 unsigned char*,因为 unsigned char 占 1 字节,且是无符号类型,可以正确比较每个字节的数值(0~255)。使用 const 保持只读语义。
  • for (size_t i = 0; i < num; i++):循环 num 次,逐字节比较。
  • if (p1[i] != p2[i]):如果当前字节不相等,说明找到了第一个差异位置。
    • return p1[i] - p2[i];:返回两个字节的差值。如果 p1[i] > p2[i],返回正数;如果 p1[i] < p2[i],返回负数。这个差值的符号与标准 memcmp 一致。
  • return 0;:如果循环结束都没有找到不相等的字节,说明两个内存块的前 num 个字节完全相同,返回 0。

测试代码

c 复制代码
#include <stdio.h>

int main()
{
    int arr1[] = {1, 2, 3, 4, 5};
    int arr2[] = {1, 2, 3, 4, 6};
    int arr3[] = {1, 2, 3, 4, 5};

    int result1 = my_memcmp(arr1, arr2, sizeof(arr1));
    int result2 = my_memcmp(arr1, arr3, sizeof(arr1));

    printf("arr1 vs arr2: %d\n", result1);  // 输出负数(arr1 < arr2)
    printf("arr1 vs arr3: %d\n", result2);  // 输出 0(相等)

    return 0;
}

输出结果

复制代码
arr1 vs arr2: -1
arr1 vs arr3: 0

关键点总结

  • my_memcmp字节逐位比较,不关心数据类型。
  • 使用 unsigned char* 而非 char*,因为 char 在某些平台上可能是有符号的,会导致负数比较结果异常。
  • 找到第一个不相等的字节后立即返回差值,不需要继续比较后续字节,效率较高。
  • 所有字节都相等时返回 0。

总结对比表

函数 功能 支持重叠 头文件
memcpy 内存块拷贝 ❌ 不支持 <string.h>
memmove 内存块拷贝 ✅ 支持 <string.h>
memset 内存块设置 --- <string.h>
memcmp 内存块比较 --- <string.h>
相关推荐
FlagOS智算系统软件栈1 小时前
众智FlagOS完成腾讯混元MT2多语翻译模型全系列多芯片适配:英伟达/华为/平头哥三芯开箱即用
开发语言·人工智能·开源
lly2024061 小时前
Docker 安装 MySQL
开发语言
深蓝电商API1 小时前
爬虫代理IP智能调度:基于响应速度的实时评分算法
爬虫·算法
汉克老师1 小时前
GESP5级C++考试语法知识(十七、二分算法提高篇(一))
c++·算法·二分算法·gesp5级·gesp五级·二分算法易错点
techdashen1 小时前
在 Async Rust 中实现请求合并(Request Coalescing)
开发语言·后端·rust
灵智实验室1 小时前
PX4状态估计技术EKF2详解(五):EKF2 故障检测、重置与鲁棒性——从单实例到多实例仲裁
算法·无人机·px 4
RSTJ_16251 小时前
PYTHON+AI LLM DAY FIFITY-THREE
开发语言·人工智能·python
programhelp_1 小时前
Roblox Coding OA 面经分享|题量不小,但整体更偏工程思维
人工智能·算法·面试
JAVA社区1 小时前
Java进阶全套教程(一)—— 数据框架Mybatis详解
java·开发语言·面试·职场和发展·mybatis