
👋 你是否经常被这些“看起来差不多”的内存函数搞糊涂? “memcpy 和 memmove 到底有什么区别?” “memset 填充的是字符还是整数?” “memcmp 为什么比较不出字符串的大小?” 别急,这篇文章我们从最基础的原理、内存图示、代码示例、常见坑与模拟实现一步步讲清楚! 阅读完这篇,你不仅能用,还能“讲给别人听”。
在 C 语言中,内存函数(memory functions) 是直接操作内存字节的函数族,位于 <string.h> 中。
它们不依赖 \0 结束符,也不关心数据类型。
函数 | 功能 | 是否允许重叠 | 返回类型 |
|---|---|---|---|
memcpy() | 拷贝内存 | ❌ 否 | void* |
memmove() | 拷贝(重叠安全) | ✅ 是 | void* |
memset() | 填充内存 | — | void* |
memcmp() | 比较内存 | — | int |
🧩 一句话总结: 掌握这四个函数,你才能真正理解指针、数组和内存的本质。
void *memcpy(void *destination, const void *source, size_t num);memcpy 是 C 语言中用于内存块复制的标准库函数,定义在 <string.h> 中。
src 开始,连续复制指定字节数 count 到目标地址 dst,适用于任意类型的数据(数组、结构体、缓冲区等)。'\0' 结束符,也不会检测内存重叠,因此在源与目标区域重叠时可能导致未定义行为。从 source 拷贝 num 个字节到 destination。
不会在遇到 '\0' 时停止,也不判断重叠。
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = {0};
memcpy(arr2, arr1, 20); // 复制 20 字节(假设 int=4 字节)
for (int i = 0; i < 10; i++)
printf("%d ", arr2[i]);
return 0;
}🖨️ 输出结果:
1 2 3 4 5 6 7 8 9 10memcpy 的核心思想是逐字节复制内存内容。 函数接收目标指针 dst、源指针 src 和要复制的字节数 count。 首先保存目标首地址 ret 以便返回,然后通过 assert 检查指针有效性。接着把 dst 与 src 强制转换为 char*,因为在 C 语言中只有 char 类型能安全访问任意对象的每一个字节。 随后使用 while(count--) 循环,从源地址起始位置开始,将每个字节依次写入目标区域,每复制一个字节就让指针前进一位,直到复制完所有字节。void *my_memcpy(void *dst, const void *src, size_t count)
{
void *ret = dst;
assert(dst && src);
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
return ret;
}memcpy 的本质特征:按字节操作、与数据类型无关、不依赖字符串结束符 '\0'。 它能高效复制任意类型的数据块(如数组、结构体等),但不能处理重叠内存,否则源数据会被覆盖导致结果未定义。 这也是与 memmove 的根本区别。⚠️ 注意: memcpy 无法处理重叠区域。遇到重叠请使用 memmove。
void *memmove(void *destination, const void *source, size_t num);它与 memcpy 功能类似,都是将指定数量的字节从源地址 src 复制到目标地址 dst,但 memmove 能正确处理内存区域重叠的情况。 当检测到源和目标区域重叠时,memmove 会自动选择复制方向(从前向后或从后向前),确保数据不会被覆盖。
#include <stdio.h>
#include <string.h>
int main() {
int arr[] = {1,2,3,4,5,6,7,8,9,10};
memmove(arr + 2, arr, 20); // arr+2 与 arr 重叠
for (int i = 0; i < 10; i++)
printf("%d ", arr[i]);
return 0;
}🖨️ 输出:
1 2 1 2 3 4 5 8 9 10模拟实现 memmove 的核心思路是先判定是否存在内存重叠风险,再选择合适的复制方向来避免覆盖源数据:当 dst <= src 或 dst 在 src + count 之外(即两块区域不重叠或 dst 在 src 低地址一侧)时,采用前向拷贝(从低地址到高地址);否则说明 dst 落在 src 的后半段,存在覆盖风险,就改用反向拷贝(从高地址到低地址)。这种方向选择是 memmove 与 memcpy 的关键差异,能安全处理重叠区域。
void *my_memmove(void *dst, const void *src, size_t count)
{
void *ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count))
{
while (count--)
{
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
}
else
{
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;
}实现上将指针统一强转为 char*,以逐字节复制,保证对任意对象表示的合法访问;每次复制后移动指针并递减 count,直到复制完成。函数入口处把 dst 备份到 ret,最终按标准约定返回目标起始地址,便于链式或后续使用。整体时间复杂度 O(n)、空间 O(1),适合通用场景。
ptr 开始的连续 num 个字节,全部设置为给定的值 value(按字节填充)。
常用于数组、结构体或缓冲区的快速清零、初始化等操作。memset 按 字节 工作,并不会把整数数组设为某个具体数字(例如 memset(arr, 1, sizeof(arr)) 只是将每个字节设为 0x01)。正确使用时,它是 C 语言中最高效的内存初始化工具之一。
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "hello world";
memset(str, 'x', 6);
printf("%s", str);
return 0;
}🖨️ 输出:
xxxxxxworldptr1 和 ptr2 指向的地址开始,逐字节比较前 num 个字节的内容。
如果两块内存完全相同则返回 0;若第一个不相同的字节中,ptr1 的值大于 ptr2 的值则返回正数,否则返回负数。strcmp 不同,memcmp 不依赖字符串结束符 '\0',因此可用于比较任意类型的二进制数据,如结构体、文件缓冲区等。它常用于内存校验、排序或验证数据是否一致的底层操作中。
#include <stdio.h>
#include <string.h>
int main() {
char buffer1[] = "DWgaOtP12df0";
char buffer2[] = "DWGAOTP12DF0";
int n = memcmp(buffer1, buffer2, sizeof(buffer1));
if (n > 0)
printf("'%s' > '%s'\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' < '%s'\n", buffer1, buffer2);
else
printf("'%s' == '%s'\n", buffer1, buffer2);
return 0;
}🖨️ 输出:
'DWgaOtP12df0' > 'DWGAOTP12DF0'函数 | 功能 | 是否重叠安全 | 典型用途 |
|---|---|---|---|
memcpy | 内存拷贝 | ❌ 否 | 快速复制数据 |
memmove | 内存拷贝(重叠安全) | ✅ 是 | 缓冲区移动 |
memset | 内存填充 | — | 初始化数组、结构体 |
memcmp | 内存比较 | — | 校验、排序、验证 |