【C语言】内存函数
本文介绍了C语言中四个重要的内存操作函数: memcpy:内存拷贝函数,逐字节复制数据,要求源和目标内存不重叠。 memmove:类似memcpy,但能正确处理内存重叠的情况。 memset:内存置数函数,以字节为单位填充指定值,常用于清零或初始化。 memcmp:内存比较函数,按字节比较两个内存区域的内容。 文章详细说明了各函数的原型、特点、使用示例和常见错误,并提供了模拟实现代码。重点强调了内
·
目录
一、memcpy函数的使用和模拟实现(内存拷贝)
1.函数原型与特点
原型:
void *memcpy(void *dest, const void *src, size_t n);
头文件:
<string.h>参数:
dest- 目标内存地址
src- 源内存地址
n- 要复制的字节数返回值: 返回目标内存地址的指针(即
dest的地址)功能: 从源内存地址复制
n个字节到目标内存地址特点:
- 不知道数据的类型,只是逐字节复制(char*)
- 源和目标内存不能重叠(如果重叠,使用memmove)
- 复制指定的字节数,不会自动添加结束符\0
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
// 示例1: 复制整型数组
int src_arr[5] = {1, 2, 3, 4, 5};
int dest_arr[5];
memcpy(dest_arr, src_arr, sizeof(src_arr));
printf("Copied array: ");
for (int i = 0; i < 5; i++) {
printf("%d ", dest_arr[i]);
}
printf("\n");
// 示例2: 复制字符串
char src_str[] = "Hello, World!";
char dest_str[50];
memcpy(dest_str, src_str, strlen(src_str) + 1); // +1 复制\0
printf("Copied string: %s\n", dest_str);
// 示例3: 复制部分数据
int numbers[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int partial_copy[5];
memcpy(partial_copy, numbers + 3, 5 * sizeof(int)); // 复制索引3开始的5个整数
printf("Partial copy: ");
for (int i = 0; i < 5; i++) {
printf("%d ", partial_copy[i]);
}
printf("\n");
return 0;
}
3.注意事项与常见错误
/****************************** 1.源和目标内存不允许重叠 ******************************/
// 错误:源和目标内存重叠
char str[20] = "Hello, World!";
memcpy(str + 7, str, 7); // 未定义行为!
// 正确:使用memmove处理重叠内存
memmove(str + 7, str, 7); // 正确
/****************************** 2.目标缓冲区不能太小 ******************************/
// 错误:目标缓冲区太小
char src[100] = "This is a long string...";
char dest[10];
memcpy(dest, src, sizeof(src)); // 缓冲区溢出!
// 正确:确保目标缓冲区足够大
char dest_safe[100];
memcpy(dest_safe, src, sizeof(src)); // 正确
/****************************** 3.memcpy不会补充\0 ******************************/
// 错误:复制字符串时忘记\0
char src[] = "Hello";
char dest[10];
memcpy(dest, src, strlen(src)); // 只复制了5个字节,没有\0
// dest现在不是有效的C字符串
// 正确:包括\0
memcpy(dest, src, strlen(src) + 1); // +1 复制\0
4.模拟实现
/***************************** 方法一:逐字符复制 *****************************/
void *my_memcpy1(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL || n == 0) {
return dest;
}
// 转换为字节指针
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
// 逐字节复制
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
return dest;
}
/***************************** 方法二:使用指针 *****************************/
void *my_memcpy2(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL || n == 0) {
return dest;
}
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
// 使用指针而不是索引
while (n--) {
*d++ = *s++;
}
return dest;
}
二、memmove函数的使用和模拟实现(内存拷贝)
1.函数原型与特点
原型:
void *memmove(void *dest, const void *src, size_t n);
参数:
dest- 目标内存地址
src- 源内存地址
n- 要复制的字节数返回值: 返回目标内存地址的指针(即
dest的地址)功能: 从源内存地址复制
n个字节到目标内存地址,可以正确处理内存重叠的情况特点:
- 允许内存重叠
- 以字节为单位复制
- 不会补充\0
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
// 示例1: 非重叠内存复制(与memcpy相同)
int src_arr[5] = {1, 2, 3, 4, 5};
int dest_arr[5];
memmove(dest_arr, src_arr, sizeof(src_arr));
printf("Copied array: ");
for (int i = 0; i < 5; i++) {
printf("%d ", dest_arr[i]);
}
printf("\n");
// 示例2: 重叠内存复制(memmove的关键特性)
char str[20] = "Hello, World!";
printf("Before memmove: %s\n", str);
// 将字符串的前6个字符复制到从第7个字符开始的位置
memmove(str + 7, str, 6);
printf("After memmove: %s\n", str); // 输出: Hello, Hello!
// 示例3: 反向重叠(目标在源之前)
char str2[20] = "Hello, World!";
memmove(str2, str2 + 7, 6);
printf("Backward overlap: %s\n", str2); // 输出: World!World!
return 0;
}
3.注意事项与常见错误
/****************************** 1.是以字节为单位复制 ******************************/
// 错误:使用错误的长度
int src_arr[5] = {1, 2, 3, 4, 5};
int dest_arr[5];
// 错误:忘记乘以元素大小
memmove(dest_arr, src_arr, 5); // 只复制了5个字节,不是5个整数!
// 正确:使用sizeof
memmove(dest_arr, src_arr, 5 * sizeof(int)); // 正确
// 或者
memmove(dest_arr, src_arr, sizeof(src_arr)); // 正确
/****************************** 2.目标缓冲区不能太小 ******************************/
// 错误:目标缓冲区太小
char src[100] = "This is a long string...";
char dest[10];
memmove(dest, src, sizeof(src)); // 缓冲区溢出!
// 正确:确保目标缓冲区足够大
char dest_safe[100];
memmove(dest_safe, src, sizeof(src)); // 正确
// 或者只复制安全数量的字节
size_t safe_size = sizeof(dest_safe) < sizeof(src) ? sizeof(dest_safe) : sizeof(src);
memmove(dest_safe, src, safe_size);
/****************************** 3.memmove不会补充\0 ******************************/
// 错误:复制字符串时忘记\0
char src[] = "Hello";
char dest[10];
memmove(dest, src, strlen(src)); // 只复制了5个字节,没有\0
// dest现在不是有效的C字符串
// 正确:包括\0
memmove(dest, src, strlen(src) + 1); // +1 复制\0
4.模拟实现
/***************************** 方法一:检查重叠并选择复制方向 *****************************/
void *my_memmove1(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL || n == 0) {
return dest;
}
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
// 检查内存是否重叠
if (s < d && s + n > d) {
// 重叠且目标在源之后,从后往前复制
for (size_t i = n; i > 0; i--) {
d[i - 1] = s[i - 1];
}
} else {
// 不重叠,或目标在源之前,从前往后复制
for (size_t i = 0; i < n; i++) {
d[i] = s[i];
}
}
return dest;
}
/***************************** 方法一:使用指针 *****************************/
void *my_memmove2(void *dest, const void *src, size_t n) {
if (dest == NULL || src == NULL || n == 0) {
return dest;
}
unsigned char *d = (unsigned char *)dest;
const unsigned char *s = (const unsigned char *)src;
if (s < d && s + n > d) {
// 重叠且目标在源之后,从后往前复制
d += n;
s += n;
while (n--) {
*(--d) = *(--s);
}
} else {
// 不重叠,或目标在源之前,从前往后复制
while (n--) {
*d++ = *s++;
}
}
return dest;
}
5.与memcpy、strcpy、strncpy的区别
| 特性 | memcpy | memmove | strcpy | strncpy |
|---|---|---|---|---|
| 用途 | 内存块复制 | 内存块复制(可重叠) | 字符串复制 | 字符串复制(指定长度) |
| 停止条件 | 复制n个字节 | 复制n个字节 | 遇到\0停止 | 复制n个字符或遇到\0 |
| 处理重叠 | 未定义行为 | 正确处理 | 未定义行为 | 未定义行为 |
| 添加\0 | 从不 | 从不 | 总是 | 只有源长度<n时 |
| 性能 | 通常最快 | 稍慢(检查重叠) | 较快 | 较慢 |
三、memset函数的使用(内存置数)
1.函数原型与特点
原型:
void *memset(void *ptr, int value, size_t num);
参数:
ptr- 指向要填充的内存块的指针
value- 要设置的值(以int形式传递,但函数会将该值转换为unsigned char)
num- 要填充的字节数返回值: 返回指向内存块
ptr的指针功能: 将
ptr指向的内存块的前num个字节设置为value的值特点:
- 以字节为单位进行填充,不关心数据类型
- 即使传递负数,也会被转换为unsigned char
- 常用于清零和初始化
- 不会自动添加结束符\0,如果用作字符串,需要手动添加\0
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
// 示例1: 将数组初始化为0
int arr[10];
memset(arr, 0, sizeof(arr));
printf("Array initialized to 0: ");
for (int i = 0; i < 10; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 示例2: 将字符串数组初始化为特定字符
char str[20];
memset(str, 'A', 19);
str[19] = '\0'; // 手动添加结束符
printf("String filled with 'A': %s\n", str);
// 示例3: 将结构体清零
struct Point {
int x;
int y;
char label[10];
} point;
memset(&point, 0, sizeof(point));
printf("Point structure cleared: x=%d, y=%d, label='%s'\n",
point.x, point.y, point.label);
return 0;
}
3.注意事项与常见错误
/****************************** 1.memset是以字节为单位置数,而不是以元素为单位 ******************************/
// 错误:试图将整型数组初始化为1
int arr[10];
memset(arr, 1, sizeof(arr)); // 每个字节设为1,不是每个整数设为1
// arr[0]将是0x01010101(16843009),而不是1
// 正确:使用循环初始化整型数组
for (int i = 0; i < 10; i++) {
arr[i] = 1;
}
// 正确:使用memset清零
memset(arr, 0, sizeof(arr)); // 所有字节设为0,每个整数为0
/****************************** 2.用于字符串时不能忘记手动设置\0 ******************************/
// 错误:填充字符串但忘记结束符
char buffer[10];
memset(buffer, 'A', 10); // 10个'A',没有\0
printf("%s\n", buffer); // 未定义行为,可能打印乱码
// 正确:预留位置给\0
memset(buffer, 'A', 9);
buffer[9] = '\0'; // 现在有9个'A'和一个\0
printf("%s\n", buffer); // 正确
/****************************** 3.用于负数时会将其转为无符号类型 ******************************/
// memset将值转换为unsigned char,所以会有截断
char buf[10];
memset(buf, 300, 10); // 300被截断为44(300 % 256)
memset(buf, -1, 10); // -1被转换为255(0xFF)
// 更清晰的写法
memset(buf, 0xFF, 10); // 明确使用无符号值
4.与calloc的区别
| 特性 | memset | calloc |
|---|---|---|
| 用途 | 设置已分配内存的值 | 分配并清零内存 |
| 参数 | 已分配的内存指针、值、字节数 | 元素数量、元素大小 |
| 内存来源 | 已有内存块 | 从堆分配新内存 |
| 清零效率 | 可以清零任意内存 | 专门用于清零新分配的内存 |
| 灵活性 | 可以设置为任意值 | 只能设置为0 |
四、memcmp函数的使用(内存比较)
1.函数原型与特点
原型:
int memcmp(const void *ptr1, const void *ptr2, size_t num);
参数:
ptr1- 指向第一个内存块的指针
ptr2- 指向第二个内存块的指针
num- 要比较的字节数返回值:
如果
ptr1<ptr2,返回负数如果
ptr1>ptr2,返回正数如果
ptr1==ptr2,返回0功能: 比较两个内存块的前
num个字节特点:
- 按字节比较,不关心数据类型
- 与strcmp不同,memcmp不会在\0处停止
- 可以比较任意内存区域
- 大小相同返回0,大小不同返回值不一定是-1或1
2.使用示例
#include <stdio.h>
#include <string.h>
int main() {
// 示例1: 比较两个字符串
char str1[] = "Hello";
char str2[] = "Hello";
char str3[] = "Hello, World!";
int result1 = memcmp(str1, str2, 5); // 比较前5个字符
printf("memcmp(\"Hello\", \"Hello\", 5) = %d\n", result1); // 输出: 0
int result2 = memcmp(str1, str3, 5); // 比较前5个字符
printf("memcmp(\"Hello\", \"Hello, World!\", 5) = %d\n", result2); // 输出: 0
int result3 = memcmp(str1, str3, 7); // 比较前7个字符
printf("memcmp(\"Hello\", \"Hello, World!\", 7) = %d\n", result3); // 输出: 负数
// 示例2: 比较整数数组
int arr1[] = {1, 2, 3, 4, 5};
int arr2[] = {1, 2, 3, 4, 5};
int arr3[] = {1, 2, 3, 4, 6};
int result4 = memcmp(arr1, arr2, 5 * sizeof(int));
printf("Array comparison (equal): %d\n", result4); // 输出: 0
int result5 = memcmp(arr1, arr3, 5 * sizeof(int));
printf("Array comparison (different): %d\n", result5); // 输出: 负数
// 示例3: 比较部分内存
unsigned char data1[] = {0x01, 0x02, 0x03, 0x04, 0x05};
unsigned char data2[] = {0x01, 0x02, 0x03, 0x04, 0x06};
int result6 = memcmp(data1, data2, 4); // 比较前4个字节
printf("Partial comparison (equal): %d\n", result6); // 输出: 0
int result7 = memcmp(data1, data2, 5); // 比较所有5个字节
printf("Full comparison (different): %d\n", result7); // 输出: 负数
return 0;
}
3.注意事项与常见错误
/****************************** 1.字节顺序问题 ******************************/
// 在不同字节序的机器上,memcmp结果可能不同
uint32_t a = 0x12345678;
uint8_t b[4] = {0x12, 0x34, 0x56, 0x78};
// 在大端机器上:memcmp(&a, b, 4) == 0
// 在小端机器上:memcmp(&a, b, 4) != 0
// 解决方案:使用网络字节序(大端)或手动比较
/****************************** 2.比较特殊的数据类型可能无意义 ******************************/
// 注意:比较不同类型的数据可能没有意义
int a = 1;
float b = 1.0;
int result = memcmp(&a, &b, sizeof(int)); // 无意义,比较位模式
// 浮点数的特殊值可能有问题
float f1 = -0.0;
float f2 = 0.0;
int r = memcmp(&f1, &f2, sizeof(float)); // 可能返回非0,尽管数学上相等
更多推荐



所有评论(0)