C语言中的内存函数(memcpy, memmove, memcmp, memset)
目录
一、内存拷贝函数——memcpy
✍️介绍
✍️模拟实现
二、内存拷贝函数——memmove
✍️介绍
✍️模拟实现
三、内存比较函数——memcmp
四、内存设置函数——memset
一、内存拷贝函数——memcpy
✍️介绍
这个函数是用来从一个内存位置拷贝一块内存到另一个内存位置的函数,使用的时候的头文件是string.h
函数声明:
void* memcpy(void* dest, const void* src, size_t n);
参数说明:
- 第一个参数:是目标位置的地址,因为我们不知道用户传入的参数类型是什么,所以我们一律使用了void*来作为参数类型。
- 第二个参数:是源位置的地址,这里要注意的是这里的类型最好是加上const,这是因为用户不能修改源来位置的内容。
- 第三个参数:要复制的字节个数。
举个栗子:
#include <stdio.h>
#include <string.h>int main() {int arr1[] = {1, 2, 3, 4, 5};int arr2[5] = {0};memcpy(arr2, arr1, 8);for(int i = 0; i < 5; i++) {printf("%d ", arr2[i]);}return 0;
}
测试结果:
敲黑板:
我们将从src地址开始的n个字节的内容复制到了dest地址,如果src和dest区域出现了重叠的话,这个时候的结果就是未定义的,也就是无法保证正确的复制了。
✍️模拟实现
我们首先要做就是要保存住dest指针,这是为了方便我们后续的返回操作,然后就是遍历count次一个字节一个字节的将src位置开始的数据拷贝到dest,最后我们再返回原来我们保存的dest指针。
void* my_memcpy(void* dest, const void* src, size_t count) {assert(src != NULL); // 断言不要传入一个空的地址assert(dest != NULL); // 断言不要传入一个空的地址void* temp = dest; // 存一下dest的地址while(count--) // 遍历count次{*((char*)dest) = *((char*)src); // 这里重点解释一下:我们这里使用char*强转就是为了限制每一次都是一个字节的操作,前面的*是找到地址指向的数据(char*)dest++;(char*)src++;}return temp;
}
测试一下我仿写的效果:
二、内存拷贝函数——memmove
✍️介绍
其实这个函数和我们上面的memcpy函数的函数参数都是一样的,同时我上面也提及了memcpy函数无法解决我们的src和dest区域的重叠问题而这个函数就可以解决这个问题。
函数声明:
void* memmove(void* dest, const void* src, size_t n);
参数说明:
这里的参数和我们上面的一样,这里就不多说了。
我们这里首先来看看上面这个函数重叠的栗子:
我们给定一个从1到10的数组,然后我将1,2,3,4拷贝到3,4,5,6上,预期的结果是1 2 1 2 3 4 7 8 9 10
#include <stdio.h>
#include <string.h>int main() {int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};memcpy(arr + 2, arr, 16);for(int i = 0; i < 10; i++) {printf("%d ", arr[i]);}return 0;
}
但是我们的运行结果就是预期的结果,这是因为我们的编译器发现了这个内存重叠问题,于是编译器便自作主张的使用了memmove函数。
但是如果不考虑编译器的优化,我的结果应该是1 2 1 2 1 2 7 8 9 10。
这里我们根据上面我们实现的原理来走一遍,dest和src同时往后走,3变成了1,4变成了2,这个时候我们的5就变成了1,7就是2了。
✍️模拟实现
我们这里可以首先发现一个规律,就是我们的拷贝操作实际上是有三类的:
我们通过上面的图,总结一下:
- 第一类:dest指针在src内存块的左边,我们这里采用从前到后的拷贝方法,这样就可以避免重叠了。
- 第二类:dest指针位于src内存块内,采用从后到前的拷贝方法,这样就可以避免重叠了。
- 第三类:dest指针位于src内存块的右边,我们采用从后到前和从前到后都是可以的。
代码如下:
void* memmove(void* dest, const void* src, size_t count) {assert(src != NULL); // 断言不要传入一个空的地址assert(dest != NULL); // 断言不要传入一个空的地址void* temp = dest; // 存一下dest的地址if(dest < src) {while(count--) {*((char*)dest) = *((char*)src);(char*)dest++;(char*)src++;} }else {while(count--) {*((char*)dest + count) = *((char*)src + count); // 从后向前}}return temp;
}
测试效果如下:
三、内存比较函数——memcmp
这个函数是用来比较两个内存区域,也就是比较ptr1和ptr2指向的内存区域的前n个字节,当ptr1小于ptr2就返回负数,当ptr1大于ptr2就返回正数。
函数声明:
int memcmp(const void* ptr1, const void* ptr2, size_t n);
参数说明:
- 第一个参数:是要比较的第一个内存空间的地址
- 第二个参数:是要比较的第二个内存空间的地址
- 第三个参数:是要比较的字节个数
我们这里可以写个代码来测试一下:
#include <stdio.h>
#include <string.h>int main() {int arr1[] = {1, 2, 3, 4};int arr2[] = {1, 2, 4, 5};int ret1 = memcmp(arr1, arr2, 8);int ret2 = memcmp(arr1, arr2, 9);printf("%d %d", ret1, ret2);return 0;
}
测试结果如图:
我们这里解释一下,我们在使用的编译器是小端模式,所以它的存储模式是从低地址到高地址的,比如这里的arr1和arr2:
所以我们比较前8个字节就会返回0,而比较到了第9个字节的时候就会返回-1了。
四、内存设置函数——memset
这个函数的主要目的就是将一块内存设置特定的值。
函数声明:
void* memset(void* ptr, int value, size_t n);
参数说明:
- 第一个参数:就是要设置的内存的开始位置了。
- 第二个参数:要设置的内容,这里虽然是int,但是后面会被转换成unsigned char类型的,也就说明了这个函数是一个字节一个字节设置的。
- 第三个参数:是从起始位置开始需要设置的字节数了。
我们举个栗子:
比如将"hello world!"字符串的前5个字符设置成'#'。
代码如下:
#include <stdio.h>
#include <string.h>int main() {char string[] = "hello world!";memset(string, '#', 5);printf("%s", string);return 0;
}
效果如下图: