紧接着前面的字符串函数,本章我们将要介绍的是C语言有关内存方面的函数
目录
1. memcpy 使用和模拟实现
1.1 函数的介绍与作用
在C语言中,mem是内存的意思,cpy又是copy的缩写,即拷贝。所以我们从函数名中不难推测出该函数的作用——将一块内存上的内容拷贝到另一块内存中。
void * memcpy ( void * destination, const void * source, size_t num );
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
- 这个函数在遇到 '\0' 的时候并不会停下来
- 如果source和destination有任何的重叠,复制的结果都是未定义的
- memcpy函数是以字节为单位进行拷贝的,不是以单个元素,所以像拷贝int类型的数据时要特别注意每个元素是4个字节。
使用范例:(省略了打印的语句)
1.2 memcpy函数的模拟
在简单了解memcpy函数后,让我们试试简单模拟下memcpy函数
void* sim_memcpy(void* new, const void* orgin, size_t num)
{
assert(new);
assert(orgin);
while (num--)
{
*(char*)new = *(char*)orgin;
new = (char*)new + 1;
orgin = (char*)orgin + 1;
}
}
- void*是为了用于接收任何类型的地址
- 关于为什么要强制类型转换成char*,这是因为char*类型指针解引用可访问一个字节。方便我们遍历所有数据。
2. memmove 使用和模拟实现
2.1 函数的介绍与作用
memcpy函数的拷贝是作用于两块不同且没有重叠的内存空间,那如果我们要拷贝的内存空间与源空间存在重叠部分(例如下图所示),这时候我们是否可以继续使用memcpy函数呢?
示范结果:
我们发现memcpy函数也可以达到我们想要的结果,那是不是意味着我们的memcpy模拟函数也可以达到相同的效果呢?我们再试一下。
结果跟上面完全不同。
这是怎么回事呢?是我们的memcpy函数有问题吗?
其实真相是,在面对存在重叠空间的情况,我们C语言有一个专门用来处理这种情况的内存函数——memmove函数。
而至于为什么我们使用memcpy函数也能得出正确的结果,我个人猜想这是因为vs的开发者对这两个函数有进行独特的处理,使得memcpy函数也可以完成memmove函数的工作。但这不重要,重要的是我们能理解这个函数的内部运行逻辑,并模拟出自己的memmove函数。
2.2 memmove函数的模拟
在模拟函数之前,我们要先明白为什么我们的·自己模拟的memcpy函数无法完成我们处理重叠内存空间的任务。
我们发现,当第一个位置的值赋给第三个位置后,’3‘变成’1‘,但当我们想把’3‘赋值给’5‘时,因为第三个位置上的值已经变成’1‘了,所以第五个位置上的值仍然还是’1’,而不是‘3’。以此类推。所以最后得到一个错误的结果。
那要怎么修改呢?我们赋值的时候是从‘1’开始向‘5‘的方向,即正向赋值的,如果我们从’5’开始反向赋值呢?
我们看到如果我们从最后一位开始赋值,便可以避免前面因为数值被覆盖而引起错误的尴尬。所以以后我们便用这种从后往前反方向赋值的方法来赋值的方法来处理重叠内存空间的问题?当然不是,这个世界并不是非黑即白,我们不能做二极管,从后往前处理也不是万能的。
举个最简单例子我们将前面的例子反过来:
如果我们继续使用从后往前的方法来解决,会出现出什么情况呢?
我们看到,它又会出现之前数值被覆盖的尴尬错误。
其实我们仔细观察就会发现:
- 当new(新地址的首地址)在orgin(源地址的首地址)左边👈(前面)时,此时我们需要采用从前向后的方法;
- 当new(新地址的首地址)在orgin(源地址的首地址)右边👉(前面)时,此时我们需要采用从后向前的方法;
- 当new(新地址的首地址)与orgin(源地址的首地址)没有重叠空间时,此时上述两种方法均可;
模拟函数的代码
//sim_memmove
void* sim_memmove(void* new, const void* orgin, size_t num)
{
void* ret = new;
if ((new <= orgin) || (char*)new >= (char*)orgin + num)
{
while (num--)
{
*(char*)new = *(char*)orgin;
new = (char*)new + 1;
orgin = (char*)orgin + 1;
}
}
else
{
while(num--)
{
*((char*)new + num) = *((char*)orgin + num);
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7 };
sim_memmove(arr1 + 2, arr1, 20);
for (int z = 0; z < 7; z++)
{
printf("%d, ", arr1[z]);
}
printf("\n");
}
运行结果:
使用memmove函数的运行结果:
结果显示我们跟库函数memcpy和memmove函数是一样的。这也说明我们的模拟的成功的。
3. memset 函数的使用
void * memset ( void * ptr, int value, size_t num );
memset是⽤来设置内存的,将内存中的值以字节为单位设置成想要的内容。
- ptr是指向被填充空间的指针,
- value是要设置的值,
- num则是总共要被设置的值。
- 有个地方我们要特别注意,就是memset函数是以字节为单位进行设置的,不能以元素来设置。
示例代码:
#include <stdio.h>
#include <string.h>
int main ()
{
char str[] = "hello world";
memset (str,'x',6);
printf(str);
return 0;
}
运行结果:
4. memcmp 函数的使用
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
- ⽐较从ptr1和ptr2指针指向的位置开始,向后的num个字节
- 返回值如下:
示例代码
#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' is greater than '%s'.\n", buffer1, buffer2);
else if (n < 0)
printf("'%s' is less than '%s'.\n", buffer1, buffer2);
else
printf("'%s' is the same as '%s'.\n", buffer1, buffer2);
return 0;
}
运行结果:
我们也花了
5. 总结
以上就是本章的全部内容了,比较重要的一个函数也就是memmove函数了,我们也花了最多的篇幅也去介绍。其实细心的同学会发现,memmore函数就是一个万金油函数,而且我们的内存函数是完全可以替代之前提到过的字符串函数。
最后感谢看到最后的每一位同学。有任何错误或者不足请帮忙在评论区指出。谢谢
完