在 C 语言中,直接操作内存是一项核心能力,也是区分 C 语言与其他高级语言的重要特性之一。memset 和 memcpy 是 <string.h> 头文件提供的两个最基础、最常用的内存操作函数。它们虽然都用于操作内存,但用途和行为却截然不同。

本文将详细解析这两个函数,并通过丰富的示例代码帮助你彻底掌握它们

1. memset:内存初始化函数

1.1 函数原型
#include <string.h>
void *memset(void *ptr, int value, size_t num);
1.2 功能说明

memset 函数用于将一块内存区域的前 num 个字节都设置为指定的字节值 value

  • ptr: 指向要填充的内存块的起始地址。
  • value: 要设置的值。虽然参数类型是 int,但实际上只有它的 低8位(一个字节) 会被使用。
  • num: 要设置的字节数。
  • 返回值: 返回指向内存块起始地址的指针(即 ptr)。
1.3 常见用途
  • 初始化数组或结构体:将其内容清零或设置为特定值。
  • 填充缓冲区:为即将写入数据的缓冲区预设一个值(例如,用 0xFF 填充)。
  • 重置内存:在释放内存前,将敏感数据(如密码)覆盖掉。
1.4 示例代码
示例 1:初始化整型数组为 0
#include <stdio.h>
#include <string.h>

int main() 
{
    int arr[5];
    int i;

    // 使用 memset 将数组的每个字节都设置为 0
    // sizeof(arr) 计算数组总字节数
    memset(arr, 0, sizeof(arr));

    printf("数组初始化后的值:");
    for (i = 0; i < 5; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

输出:
数组初始化后的值:0 0 0 0 0 

解析memset 会遍历 arr 数组在内存中的前 sizeof(arr) (即 5 * sizeof(int)) 个字节,并将每个字节都设置为 0。对于 int 类型(通常为 4 字节),每个 int 元素都会变成 0x00000000,即整数 0

示例 2:初始化字符数组(字符串)
#include <stdio.h>
#include <string.h>

int main() 
{
    char str[20];

    // 将字符串前 10 个字节设置为 'A'
    memset(str, 'A', 10);
    
    // 手动添加字符串结束符
    str[10] = '\0';

    printf("字符串内容: %s\n", str);

    return 0;
}

输出:
字符串内容: AAAAAAAAAA

解析memset(str, 'A', 10) 将 str 指向的内存块的前 10 个字节都设置为字符 'A' 的 ASCII 值(0x41)。注意,memset 不会自动添加字符串结束符 \0,需要手动处理。


示例 3:一个常见的陷阱

初学者很容易犯一个错误,试图用 memset 设置一个非零的值,例如:

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

int main() 
{
    int arr[5];
    int i;

    // 错误用法:试图将数组元素设置为 1
    memset(arr, 1, sizeof(arr));

    printf("错误用法的结果:");
    for (i = 0; i < 5; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

可能的输出(在 32 位系统上):
错误用法的结果:16843009 16843009 16843009 16843009 16843009 

解析memset 是按字节填充的。memset(arr, 1, sizeof(arr)) 会将数组的每个字节都设置为 1。对于一个 4 字节的 int,它的内存表示会是 0x01010101,其十进制值为 16843009,而不是我们期望的 1

如何正确地将数组元素设置为 1? 只能通过循环:

for (i = 0; i < 5; i++) 
{
    arr[i] = 1;
}

2. memcpy:内存复制函数

2.1 函数原型
#include <string.h>
void *memcpy(void *dest, const void *src, size_t num);
2.2 功能说明

memcpy 函数用于从源内存地址 src 复制 num 个字节 的数据到目标内存地址 dest

  • dest: 目标内存块的起始地址。
  • src: 源内存块的起始地址。
  • num: 要复制的字节数。
  • 返回值: 返回指向目标内存块起始地址的指针(即 dest)。
2.3 常见用途
  • 复制数组:将一个数组的内容复制到另一个数组。
  • 复制结构体:整体复制一个结构体变量的内容。
  • 处理二进制数据:在缓冲区之间复制任意类型的二进制数据。

2.4 示例代码
示例 1:复制整型数组
#include <stdio.h>
#include <string.h>

int main() 
{
    int source[5] = {10, 20, 30, 40, 50};
    int destination[5];
    int i;

    // 使用 memcpy 复制数组
    memcpy(destination, source, sizeof(source));

    printf("源数组: ");
    for (i = 0; i < 5; i++) 
    {
        printf("%d ", source[i]);
    }
    printf("\n");

    printf("目标数组: ");
    for (i = 0; i < 5; i++) 
    {
        printf("%d ", destination[i]);
    }
    printf("\n");

    return 0;
}

源数组: 10 20 30 40 50 
目标数组: 10 20 30 40 50 

解析memcpy 从 source 的起始地址开始,复制 sizeof(source) 个字节的数据到 destination 指向的内存区域。


示例 2:复制结构体
#include <stdio.h>
#include <string.h>

struct Person 
{
    char name[20];
    int age;
    float height;
};

int main() 
{
    struct Person p1 = {"Alice", 30, 1.65f};
    struct Person p2;

    // 使用 memcpy 复制整个结构体
    memcpy(&p2, &p1, sizeof(struct Person));

    printf("Person 1: Name: %s, Age: %d, Height: %.2f\n", p1.name, p1.age, p1.height);
    printf("Person 2: Name: %s, Age: %d, Height: %.2f\n", p2.name, p2.age, p2.height);

    return 0;
}

Person 1: Name: Alice, Age: 30, Height: 1.65
Person 2: Name: Alice, Age: 30, Height: 1.65

解析memcpy 可以非常方便地将一个结构体变量的所有成员一次性复制到另一个结构体变量中。


示例 3:memcpy 的一个重要限制

memcpy 的一个关键注意事项是:它不检查源内存和目标内存是否重叠。如果 dest 和 src 指向的内存区域有重叠,memcpy 的行为是未定义的,可能会导致数据丢失或损坏。

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

int main() 
{
    char buffer[20] = "Hello, World!";
    printf("原始数据: %s\n", buffer);

    // 错误用法:源地址和目标地址重叠
    // 意图:将 "World!" 复制到 "Hello, " 后面,期望得到 "Hello, World!" (虽然这里结果可能对,但这是巧合)
    // 更危险的情况是当 dest 在 src 前面时
    memcpy(buffer + 7, buffer, 6); // 从 buffer[0] 复制 6 个字节到 buffer[7]

    printf("重叠复制后: %s\n", buffer);

    return 0;
}

原始数据: Hello, World!
重叠复制后: Hello, Hello!

解析:在这个例子中,src 是 bufferdest 是 buffer + 7。复制开始后,buffer[0] 的 'H' 会被复制到 buffer[7],但紧接着 buffer[7] 原来的 'W' 还没被复制就被覆盖了。这会导致复制结果不正确。

如何安全地处理重叠内存? 使用 memmove 函数。memmove 函数的原型和用法与 memcpy 非常相似,但它保证在源和目标内存重叠时也能正确工作(通常通过先将数据临时保存来实现)。

memmove(buffer + 7, buffer, 6); // 安全

3. 实践建议

  1. 明确需求:如果你想初始化一块内存,用 memset。如果你想复制一块内存的数据,用 memcpy
  2. 注意数据类型和大小:始终使用 sizeof() 来计算需要操作的字节数,这可以增加代码的健壮性和可移植性。
  3. memset 的陷阱:记住 memset 是按字节工作的。memset(arr, 1, sizeof(arr)) 不会将 int 数组的每个元素设置为 1
  4. memcpy 的陷阱:当源和目标内存区域可能重叠时,务必使用 memmove 来保证数据的正确性。
  5. 头文件:使用这两个函数都需要包含 <string.h> 头文件。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐