欢迎光临
我们一直在努力

C语言详解:内存函数

1. memcpy使用和模拟实现
2. memmove使用和模拟实现
3. memset函数的使用
4. memcmp函数的使用

上文我们讲了字符串函数strcpy ,这个函数的局限性太大,只能用来拷贝字符串

今天我们学习的内存函数 就可以实现任意类型的拷贝,大大提高了实用性

1. memcpy使用和模拟实现

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

功能:

memcpy 是完成内存块拷贝的,不关注内存中存放的数据是什么

函数memcpysource的位置开始向后复制num个字节的数据到destination指向的内存

位置。

如果 source destination 有任何的重叠,复制的结果都是未定义的。

!!!(内存重叠的情况使用 memmove 就行)

参数:

destination :指针,指向目标空间,拷贝的数据存放在这里

source :指针,指向源空间,要拷贝的数据从这里来

num :要拷贝的数据占据的字节数

返回值:

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

1.1代码演示

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

int main()
{
int arr1[] = {1,2,3,4,5,6,7,8,9,10};
int arr2[10] = { 0 };

// 拷贝 20 个字节 = 5 个 int 元素(1,2,3,4,5)
memcpy(arr2, arr1, 20);

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

运行结果:

1 2 3 4 5 0 0 0 0 0

!!!这里 num 传的是 20,因为 int 占 4 字节,5 个元素就是 5*4=20 字节。

1.2memcpy手动模拟实现

void * memcpy ( void * dst, const void * src, size_t count)
{
void * ret = dst; // 保存目标起始地址,用于最后返回
assert(dst); // 断言:目标指针不能为空
assert(src); // 断言:源指针不能为空

while (count–) {
*(char *)dst = *(char *)src; // 按字节拷贝(char* 是 1 字节)
dst = (char *)dst + 1; // 目标指针向后移动 1 字节
src = (char *)src + 1; // 源指针向后移动 1 字节
}
return ret;
}

解析:

为什么用 char* 转换?

因为 memcpy 是按字节拷贝的,char* 每次移动 1 字节,刚好对应最小拷贝单位。

为什么要保存 ret?

因为循环中 dst 会被不断修改,最后需要返回原始的目标起始地址。

2. memmove使用和模拟实现

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

功能:

memmove函数也是完成内存块拷贝的

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。

memmove的使用需要包含<string.h>

参数:

destination :指针,指向目标空间,拷贝的数据存放在这里

source :指针,指向源空间,要拷贝的数据从这里来

num :要拷贝的数据占据的字节数

返回值:

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

2.1代码演示

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

int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 20); // 从 arr1 起始位置拷贝 20 字节到 arr1+2
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}

分析:

arr1 是 int 数组,每个元素占 4 字节,20 字节对应 5 个元素。

arr1 + 2 表示目标地址是数组第 3 个元素(索引 2)的位置。

源地址是 arr1(索引 0),目标地址 arr1+2(索引 2),两者内存是重叠的。

memmove 会安全地处理这种重叠,先把源数据完整保存再拷贝,不会覆盖还没复制的数据。

运行结果:

1 2 1 2 3 4 5 8 9 10

2.1memove手动模拟实现

void * memmove ( void * dst, const void * src, size_t count)
{
void * ret = dst;
if (dst <= src || (char *)dst >= ((char *)src + count)) {
// 情况1:无重叠,或者目标在源前面,按从低地址到高地址的顺序拷贝
while (count–) {
*(char *)dst = *(char *)src;
dst = (char *)dst + 1;
src = (char *)src + 1;
}
} else {
// 情况2:有重叠,且目标在源的后面,从高地址到低地址倒序拷贝
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);
}

1.先判断是否重叠,以及重叠的方向

if 条件:dst<= src(目标地址在源前面) 或 dst >= src+count(目标地址在源的拷贝范围之后)→ 无重叠,按 memcpy 的方式从前向后拷贝即可。

else 条件:目标地址落在源的拷贝范围内,且在源的后面 → 从后向前倒序拷贝,避免覆盖还没复制的数据。

2.为什么要分两种拷贝方向?

正序拷贝:适用于无重叠场景,高效简单。

倒序拷贝:专门解决目标在源后面的重叠场景,先复制后面的数据,再复制前面的数据,保证源数据不被提前覆盖

2.3memcpy与memmove对比
特性 memcpy memmove
处理内存重叠 不支持,结果未定义 支持,结果安全
拷贝方向 固定从前向后 根据重叠情况,自动选择正序 / 倒序
适用场景 源和目标内存完全独立 源和目标可能重叠
效率 略高(无需判断重叠) 略低(多了一次判断逻辑)

3.memset函数的使用

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

功能:

memset 函数是用来设置内存块的内容的,将内存中指定长度的空间设置为特定的内容。

memset 的使用需要包含 <string.h>

参数:

ptr :指针,指向要设置的内存空间,也就是存放了要设置的内存空间的起始地址。

value :要设置的值,函数将会把 value 值转换成 unsigned char 的数据进行设置的。 也就是以字节为单位来设置内存块的。

num :要设置的内存长度,单位是字节。

返回值:返回的是要设置的内存空间的起始地址。

3.1代码演示

#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf(str);
return 0;
}

运行结果:

xxxxxxworld

分析:

str 初始内容:hello world(注意 hello 后有一个空格,共 6 个字符)

memset(str, 'x', 6) 会从 str 起始地址开始,将前 6 个字节(hello)全部替换为 'x'

因此输出的前 6 个字符变为 xxxxxx,后面的 world 保持不变

3.2关键注意事项

1.按字节填充的本质memset 是单字节操作,因此:

对 char 数组填充字符(如 'x')是安全的;

对 int 数组填充时,会将每个字节都设为 value 对应的 unsigned char 值。例如:int arr[5]; memset(arr, 0, sizeof(arr)); 可以将数组清 0,但 memset(arr, 1, sizeof(arr)); 不会把每个 int 设为 1,而是设为 0x01010101(小端 / 大端下的多字节组合值)。

2.num 的单位是字节计算长度时要注意:

对数组 arr,推荐写法:memset(arr, 0, sizeof(arr));(自动计算总字节数)

避免硬编码错误,比如 int arr[10]; memset(arr, 0, 10); 只会清前 10 个字节(前 2 个 int)3.value 会被截断传入的 int 类型 value 会被截断为 unsigned char,因此传入 0x1234 实际效果和传入 0x34 是一样的。 0x1234 是一个 16 位的十六进制数,对应的二进制是:0001 0010 0011 0100 unsigned char 只能存 8 位,所以 memset 只会取它的低 8 位:0011 0100,也就是 0x34

4. memcmp函数的使用

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

功能:

比较指定的两块内存块的内容,比较从ptr1和ptr2指针指向的位置开始,向后的num个字节

memcmp 的使用需要包含 <string.h>

参数:

ptr1 :指针,指向⼀块待比较的内存块

ptr2 :指针,指向另外⼀块待比较的内存块

num :指定的比较长度,单位是字节

返回值:

4.1代码演示

#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' 大于 '%s'.\\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' 小于 '%s'.\\n", buffer1, buffer2);
else
printf("'%s' 和 '%s' 一样.\\n", buffer1, buffer2);

return 0;
}

分析:

我们逐字节对比 buffer1 和 buffer2:

前两个字符 'D' 'W' 完全相同

第三个字符:buffer1 是小写 'g'(ASCII 码 0x67),buffer2 是大写 'G'(ASCII 码 0x47)

因为 0x67 > 0x47,所以 memcmp 返回正数,程序会输出:'DWgaOtP12df0' 大于 'DWGAOTP12DF0'.

4.2关键细节

1.与strcmp对比

函数 比较方式 终止条件 适合场景
memcmp 按字节比较,固定长度 必须比较完 num 个字节,不受 '\\0' 影响 任意类型的内存块(如 int 数组、结构体)
strcmp 按字符比较,字符串专用 遇到 '\\0' 就停止 仅用于比较 C 风格字符串

 举个反例:如果字符串中间有 '\\0',strcmp 会直接截断比较,而 memcmp 会继续比较后续字节。

2.比较的是 unsigned char 值

memcmp 会把每个字节当成 unsigned char 来比较,所以有符号字符的正负不影响比较结果,只看无符号的数值大小。

3. num 的单位是字节

对数组比较时,推荐用 sizeof(数组) 自动计算长度,避免硬编码错误。

例如:int arr1[5], arr2[5]; memcmp(arr1, arr2, sizeof(arr1)); 可以安全比较两个 int 数组的全部字节。

赞(0)
未经允许不得转载:171主机测评 » C语言详解:内存函数
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址