c语言提供了几个用于内存操作的函数,包括memcpy,memmove,memset,memcmp。这些函数可以高效且可靠的处理数据,本文将简单向大家介绍一下这些函数的使用,并简单模拟实现一下。
一、memcpy的使用
void * memcpy ( void * destination, const void * source, size_t num );
memcpy函数将source的位置向后复制num个字节的内存到destination所指向的地址并返回指向目标的指针。
在使用时,我们需要注意:
1、memcpy函数在遇见字符结束标志'\0'时不会停止。
2、当source与destination的地址有任何重复的,所复制的结果就是未定义的。
3、使用时需要引用头文件<string.h>。(后面三个函数也需要用到该头文件)
我们举个例子来使用一下memcpy函数:
#include<stdio.h>
#include<string.h>
//memcpy函数的使用
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int* p = (int*)memcpy(arr2, arr1, sizeof(int) * 5);//sizeof计算出int类型的大小,乘以5代表复制5个int类型的内存
int i = 0;
while (p[i])
{
printf("%d", p[i]);
i++;
}
return 0;
}
我们定义了整形数组arr1与arr2,通过memcpy进行5个整形的复制,由于memcpy返回的指针是void类型,所以还通过强制类型转换来转化返回指针类型,并遍历打印了arr2复制后的内容。
二、memcpy函数的模拟实现
为了更好地理解memcpy函数是使用,我们下面可以简单模拟一下该函数的实现:
void* my_memcpy(void* dest, const void* sour, size_t num)
{
void* ret = dest;//定义一个新指针指向dest的初始地址,稍后返回ret就相当于返回了dest的初始地址
assert(dest && sour);//判断dest与sour是否为空指针,需要引用头文件<assert.h>
while (num--)//只复制num字节的内存,最好就是一字节一字节复制
{
*((char*)dest)++ = *((char*)sour)++;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
int* p = (int*)my_memcpy(arr2, arr1, sizeof(int) * 5);//sizeof计算出int类型的大小,乘以5代表复制5个int类型的内存
int i = 0;
while (p[i])
{
printf("%d", p[i]);
i++;
}
return 0;
}
我们以num作为while语句条件,这样就控制了复制的大小,随后通过指针遍历复制内存,由于空指针不能进行加减操作,我们就强制类型转换为char*(方便一字节一字节的复制)。最后返回初始的地址。
三、memmove函数的使用与模拟实现
void * memmove ( void * destination, const void * source, size_t num );
memmove函数与memcpy函数的使用极为相似,最大的区别就是memmove处理的源内存块与目标内存块可以存在重叠区域,而memcpy却不可以。
因此memmove的使用便不再多介绍,可以参考一下memcpy。
下面我们就自己写一个函数名为my_memmove来模拟实现一下memmove的功能。
#include<stdio.h>
//memmove的模拟实现:
void* my_memmove(void* dest, const void* sour, size_t num)//基本思想还是和memcpy差不多,只是多了复制重复内存块的功能
{//如何赋予代码复制重复内存块的功能,是思考的重点
assert(dest && sour);
void* ret = dest;
if (dest <= sour || (char*)dest >= (char*)sour + num)//大部分机器是小端储存,栈区内存增长方向是从高地址到低地址。
//也就是说,栈区中的新数据会被存储到先前数据的后面,栈指针会向低地址移动
//判断是该从后向前复制还是由前向后复制
{
while (num--)
{
*((char*)dest)++ = *((char*)sour)++;//从低地址向高地址复制
}
}
else
{
dest = (char*)dest + num - 1;//减去1方便后面num--为0跳出循环
sour = (char*)sour + num - 1;
while (num--)
{
*((char*)dest)-- = *((char*)sour)--;//由高地址向低地址复制
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
我们在my_memmove函数进行一个判断。如果目标内存区域的起始地址(dest)小于等于源内存区域的起始地址(sour),或者目标内存区域的起始地址(dest)大于等于源内存区域的结束地址(sour + num),说明两个内存区域没有重叠。
在这种情况下,我们可以直接使用while循环逐字节地从源内存区域复制数据到目标内存区域(正向复制)。循环中,每一次迭代都将源指针sour所指向的内容赋值给目标指针dest,并递增两个指针的位置,直到复制完所有的字节。
如果目标内存区域和源内存区域有重叠,我们需要保证数据不会被覆盖或失真。因此,我们通过倒序的方式进行复制,从内存区域的末尾开始(逆向复制)。
在这种情况下,我们先将目标指针dest和源指针sour都移到内存区域的末尾位置。然后使用while循环,每一次迭代都将源指针sour所指向的内容赋值给目标指针dest,并递减两个指针的位置,直到复制完所有的字节。
四、memset的使用
void * memset ( void * ptr, int value, size_t num );
#include<stdio.h>
#include<string.h>
int main()
{
char arr[] = "hello world!";
memset(arr, 'x', 5);
int i = 0;
while (arr[i])
{
printf("%c", arr[i]);
i++;
}
printf("\n");
int arr2[] = { 1,2,3 };
memset(arr2, 0, sizeof(arr2));
for ( i = 0; i < 3; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
输出情况:
xxxxx world!
0 0 0
我们通过memset函数分别给这两个数组进行参数的更改。
五、memset的模拟实现
下面是一个简单的模拟实现示例,展示了如何手动实现memset函数:
void* my_memset(void* ptr, int value, size_t num)
{
unsigned char* p = ptr;//强制类型转化为无符号字符,是为了确保正确地处理字节的范围。
while (num--)
{
*p++ = (unsigned char)value;
}
return ptr;
}
int main()
{
char arr[] = "hello world!";
my_memset(arr, 'x', 5);
int i = 0;
while (arr[i])
{
printf("%c", arr[i]);
i++;
}
printf("\n");
int arr2[] = { 1,2,3 };
my_memset(arr2, 0, sizeof(arr2));
for (i = 0; i < 3; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
当我们将一个整数值赋给char
类型时,如果该整数值超出了有符号char
类型的表示范围,可能会导致溢出和未定义行为。这也是在函数的模拟实现中选择强制类型转化为无符号字符的原因。
运行程序,结果与上面一样。
六、memcmp函数的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
此函数比较ptr1与ptr2的前num个字节的大小,并返回一个整数值来表较结果。
1、如果两个内存区域的内容完全相同,返回0。
2、如果第一个不匹配的字节在ptr1
中的值小于ptr2
中的值,返回一个负数。
3、如果第一个不匹配的字节在ptr1
中的值大于ptr2
中的值,返回一个正数。
以下是一个使用memcmp函数的事例:
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "ABDefg";
char arr2[] = "ABDeFG";
int n = memcmp(arr1, arr2, sizeof(arr1));
if (n > 0)
{
printf("arr1大于arr2\n");
}
else if (n == 0)
{
printf("两者相等\n");
}
else
{
printf("arr1小于arr2\n");
}
return 0;
}
由于第一个碰到的不同的字符是f与F,小写字符ASCII码值比较大,所以打出来的结果为:
arr1大于arr2
七、memcmp函数的模拟实现
下面是一个简单的模拟实现示例,展示了如何手动实现memcmp函数:
int my_memcmp(const void* ptr1, const void* ptr2, size_t num)
{
const unsigned char* p1 = ptr1;
const unsigned char* p2 = ptr2;
for (int i = 0; i < num; i++)
{
if (p1[i] > p2[i])
{
return 1;
}
if (p1[i ]< p2[i])
{
return-1;
}
}
return 0;
}
int main()
{
char arr1[] = "ABDefg";
char arr2[] = "ABDeFG";
int n = my_memcmp(arr1, arr2, sizeof(arr1));
if (n > 0)
{
printf("arr1大于arr2\n");
}
else if (n == 0)
{
printf("两者相等\n");
}
else
{
printf("arr1小于arr2\n");
}
return 0;
}
这里使用无符号字符的原因与memset一样。在该示例中,我们使用自定义的my_memcmp
函数比较两个整型数组是否相等。通过比较数组的每个元素,我们可以确定它们是否具有相同的值。
库函数的memcmp等函数自然没有这么简单,这些都只是我们从功能上的简单模拟,方便大家更好的理解函数。