目录
- 一、memcpy
- 1.1 代码演示
- 1.2 memcpy的模拟实现
- 二、memmove
- 2.1 代码演示
- 2.2 模拟实现(小米面试题)
- 三、memset
- 3.1 代码演示
- 3.2 总结
- 四、memcmp
- 4.1 代码演示
- 4.2 总结
- 总结
一、memcpy
(memory copy 内存复制)
之前文章中写的strcpy,strncpy函数是用来拷贝字符串的,是有局限性的,那么如何拷贝一个整型数组,或者结构体数组呢?
字符函数与字符串函数(上)
字符函数与字符串函数(下)
这就要用到memcpy了
void* memcpy( void* destination,const void* source,size_t num );
功能:
- memcpy是完成内存块拷贝的,不关注内存中存放的数据是啥
- 函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
- 如果source和destination有任何的重叠,复制的结果都是未定义的
(内存重叠的情况使用memmove就行) - memcpy的使用需要包含<string.h>
参数:
destination:指针,指向目标空间,拷贝的数据存放在这里
source:指针,指向源空间,要拷贝的数据从这里来
num:要拷贝的数据占据的字节数
返回值:
拷贝完成后,返回目标空间的起始地址
1.1 代码演示
这里再展示一个浮点数的拷贝
这里数据显示的原因是浮点数在内存中无法精确保存,数组中的浮点数都要转换为二进制存到内存中去,这里小数点后的2转换为二进制很难,很可能写到50位都无法精确凑齐0.2
当然这里不知道多少个字节也可以算一下:
1.2 memcpy的模拟实现
乍一看,是不是觉得已经漂亮的实现了,实则暗藏漏洞
假设这里source空间的数据是1,2,3,4,5,覆盖到3,4,5,6,7上
结果如图:
预想情况下的覆盖情况应该是1 2 1 2 3 4 5 8 9 10,这里却是1 2 1 2 1 2 1 8 9 10
这里当3,4被1,2覆盖了之后,3就变为1,4就变为2,此时* (char* )src走到3的时候,走完4个字节就是1覆盖5,再往后就是变为2的4覆盖6,变为1的5覆盖7。所以就会监视就会出现这样的数据结果。
这就是另一种场景(内存重叠的情况使用memmove就行),但是呢这里memcpy又能正常的完成该场景的要求,也就是超常发挥了,500块的实力干了100块的活,这就是内卷。
二、memmove
(memory move 内存移动)
void* memmove( void* destination,const void* source,size_t num );
功能:
- memmove函数也是完成内存块拷贝的
- 和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的
- memmove的使用需要包含<string.h>
参数与返回值与memcpy是一样的
2.1 代码演示
2.2 模拟实现(小米面试题)
内存重叠拷贝根据memcpy写的内容有两种情况
第一种情况
这里src<dest(数组随着下标的增长,地址是由低向高变化的),就必须从src的末尾数据5开始,向着dest的末尾数据7从后向前拷贝,否则从前向后拷贝就会出现1 2 1 2 3 4 5 8 9 10的情况。
第二种情况
当dest<src的时候,就必须从src的其实数据3开始,向着dest的起始数据1开始从前向后拷贝。这样才不会出错。
memmove底层逻辑也是基于这些情况实现的
memmove的拷贝是分三种情景的
第一种情景
这里是dest<src的情景,需要从前向后拷贝
第二种情景
这里是src<dest,需要从后向前拷贝
前两种情景都是dest与src有内存重叠的情况下的考量。
第三种情况dest的内存与src的内存已经完全脱离了,无论哪种拷贝方式都是可以的。
这里给每种情景标号。
写代码的方案就可以分两种
- 1,3从前向后拷贝,2从后向前拷贝
- 1 从前向后拷贝,2,3从后向前拷贝
我个人还是觉得方案二更好一些,找 1 与 2 3 的边界就够,方案一则要找1与2的边界,2与3的边界,相对麻烦。
#include<assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{assert(dest && src);void* ret = dest;if (dest < src)//1{//从前向后拷贝while (num--){*(char*)dest = *(char*)src;dest = (char*)dest + 1;src = (char*)src + 1;}}else{//从后向前拷贝while (num--){//跳过19个字节,指向第20个字节*((char*)dest + num) = *((char*)src + num);}}return ret;
}int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//my_memmove(arr + 2, arr, 20);my_memmove(arr, arr + 2, 20);return 0;
}
这里再说一下这里类型转换的问题
dest = (char*)dest + 1;
src = (char*)src + 1;
在表达式 (char*)src + 1 中:
(char* )src:将 src(const void* 类型)临时转换为 const char* 类型。
结果类型:表达式 (char* )src + 1 的结果类型是 const char*,指向原地址后一个字节的位置。
在赋值语句 src = (char*)src + 1; 中:
左值 src:类型为 const void*(函数参数类型)。
右值 (char* )src + 1:类型为 const char* 。
C 语言允许将任意类型的指针隐式转换为 void* 或 const void* ,因此 const char* 可以直接赋值给 const void* ,无需显式强制类型转换。
三、memset
(memory move 内存设置)
void* memset( void* ptr,int value,size_t num );
功能:
- memset函数是用来设置内存块的内容的,将内存中指定长度的控件设置为特定的内容。
- memset的使用需要包含<string,h>
参数:
ptr:指针,指向要设置的内存空间,也就是存放了要设置的内存空间的起始地址。
value:要设置的值,函数将会把value值转换成unsigned char的数据进行设置的。也就是以字节为单位来设置内存块的。
num:要设置的内存长度,单位是字节。
返回值:返回的是要设置的内存空间的起始地址。
3.1 代码演示
这是一个字符数组元素的设置,那么整型数组元素能不能设置呢?
可以看到是不可以的,memset是以字节为单位设置的,它把每一个字节设置为01,也就是1,而1的16进制的表示是0x 00 00 00 01,所以是达不到所设想的要求的。
但是它可以把整型数组的每个元素设置为0
它把每个字节设置为0,每个元素也就会变为0了。
3.2 总结
当有一块内存空间需要设置内容的时候,就可以使用memset函数,值得注意的是memset函数对内存单元的设置是以字节为单位的。
四、memcmp
(memory compare)
int memcmp( const void* ptr1,const void* ptr2,size_t num );
功能:
比较指定的两块内存块的内容,比较从ptr1和ptr2指针指向的位置开始,向后的num个字节。
memcmp的使用需要包含<string.h>
参数:
ptr1:指针,指向一块待比较的内存块。
ptr2:指针,指向另一块待比较的内存块。
num:指定的比较长度,单位是字节。
返回值:
返回一个整数值,指示内存块内容之间的关系:
返回值 | 含义说明 |
---|---|
< 0 | 两个内存块中第一个不匹配字节,在 ptr1 中对应的值(按 unsigned char 值计算)小于在 ptr2 中的值 |
0 | 两个内存块的内容相等 |
> 0 | 两个内存块中第一个不匹配字节,在 ptr1 中对应的值(按 unsigned char 值计算)大于在 ptr2 中的值 |
4.1 代码演示
这里比较前16个字节相等
但如果比较前17个呢?
结果如预期所设想。
4.2 总结
如果要比较2块内存空间的数据的大小,可以使用memcmp函数,这个函数的特点就是可以指定比较长度。
memcmp函数是通过返回值告知大小关系的。
总结
以上就是C语言内存函数的全部内容了,下午也是上网冲浪的时候发现了小米的暑期实习面试题有我所写的内容,也是非常开心哈哈,毕竟证明了自己所学都是有价值的,而且是贴近就业的,喜欢作者文章的靓仔靓女们不要忘记一键三连支持一下~
你们的支持就是我最大的动力。