C语言--内存操作函数
首先,从各个内存函数的函数名上看,都有一个mem,其实就是在英语上的对memory的简称,本意是记忆,在计算机体系中翻译为内存的意思,可以了解一下,也方便记忆。那么我们就开始学习以下几个C语言中的内存操作函数
注:与字符串函数类似,内存函数都需要加一个头文件 <string.h>
一、memcpy
memcpy可以理解为memory copy,即内存拷贝的意思,所以它的用处就是将一块内存空间的内容拷贝到另外一个内存空间。
首先我们要知道memcpy函数的各个参数和返回值的类型,如下:
void* memcpy(void* destination, const void* source, size_t num);
我们清楚地看到,memcpy函数有三个参数,返回类型为void*
- 第一个参数是 destination ,顾名思义就是目的地,就是要把拷贝的内存放到另一个目的地的内存空间中,目标空间的起始地址,类型是void*。
- source,源头的意思,就是要拷贝的内存的起始地址,类型是void*,因为我们不希望要拷贝的内存空间被改变,所以在前面用const修饰。
memcpy是对内存空间拷贝,不在乎内存中放的是什么类型的数据,所以destination和source的类型都是void*。 - num,就是要拷贝内存空间的大小,类型是size_t,以字节为单位
拷贝结束后,返回的是目标空间(destination指向的空间)的起始地址
总结它的功能就是:函数memcpy从source的位置开始向后复制num个字节的数据到destination指向的内存位置
注:在拷贝内存空间的时候,destination和source两块内存空间可能会有重叠的部分,这时的结果是未定义的,但是用memcpy函数可能也会成功,但是不建议这样使用,如果有重叠的部分,会有memmove内存函数来专门实现
1.1 memcpy的使用
首先初始化两个数组
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
然后我们想要把arr1数组中的前5个元素拷贝到arr2数组中,要用到memcpy内存函数,所以参数destination就是arr2,参数source就是arr1,因为num是以字节为单位的内存空间大小,数组元素为int类型,大小为4个字节,所以5个元素总共大小就是20个字节
memcpy(arr2, arr1, 20);
然后我们用for循环打印arr2数组
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
具体使用memcpy内存函数代码如下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
结果:
1.2 memcpy的模拟实现
了解了memcpy内存函数,那么我们可以思考一下如何自己设计一个函数来模拟实现memcpy函数
首先我们设计一个函数,函数名为my_memcpy,既然是模拟实现,与memcpy的函数参数要一样,即:
void* my_memcpy(void* dest, const void* src, size_t num)
与上文一样,在主函数中,首先创建两个数组
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
然后使用我们的my_memcpy函数
my_memcpy(arr2, arr1, 20);
最后打印
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
那么如何去具体模拟实现my_memcpy呢
首先要保证两个指针参数不能是空指针,保证安全性,所以首先要用assert函数断言一下,需要加头文件<assert.h>
assert(dest && src);
然后想一下memcpy函数是把一块内存空间的内容拷贝到另一个内存空间,要拷贝的内存空间的大小就是参数里的num,那么我们就可以有了一个思路:我们是不是可以把要拷贝的空间大小num分成一个字节一个字节的拷贝,每拷贝一次,num个字节的空间就少一个字节,num- -;所以可以使用while循环
while (num--)
{
;
}
参数dest和src都分别代表着两块内存空间的起始地址,因为这两个参数的类型是void*,所以不能对其解引用,就需要对其进行强制类型转换,那么强制转换成什么类型呢,我们思考,我们需要对内存空间一个字节一个字节的拷贝,所以将其转换成(char*)类型,然后在解引用拷贝
*(char*)dest = *(char*)src;
拷贝一次后,需要继续向后拷贝,一次跳过一个字节,因为(char*)类型的指针加1一次跳过一个字节,于是
dest = (char*)dest + 1;
src = (char*)src + 1;
所以循环如下:
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
那么此时我们已经将内存空间进行了拷贝,函数需要返回目标空间的起始地址,所以就有人说了,直接返回dest不就行了,但是细心的朋友会发现,此时dest经过拷贝过程,已经不在指向之前的目标空间了,所以我们在拷贝循环开始前需要在设置一个指针变量来放dest一开始时的地址,类型是void*
void* ret = dest;
整体函数实现如下
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
具体模拟实现memcpy函数如下:
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memcpy(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
int arr2[10] = { 0 };
my_memcpy(arr2, arr1, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr2[i]);
}
return 0;
}
结果:
二、memmove
2.1 memmove的使用
在上文中,我们知道,如果拷贝的两块空间有重叠,那么最好不要用memcpy函数,而是用memmove函数
首先,memmove的参数和返回值与memcpy的个数类型相同,
void * memmove ( void * destination, const void * source, size_t num );
比如,定义一个数组
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
我们想把1 2 3 4 5拷贝到3 4 5 6 7 的位置上,两者内存空间上有元素3 4 5内存空间的重叠,我们知道arr数组名代表数组首元素的起始地址,就是第一个元素1的起始地址,那么arr+2,1是int类型元素,+1跳过一个整型类型的大小,则arr+2就跳过2个指向第三个元素3的起始地址,要拷贝5个int类型的大小,则num就是5*sizeof(int)
memmove(arr1 + 2, arr1, 5 * sizeof(int));
然后打印
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
具体使用如下:
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr1 + 2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
结果:
2.2 memmove的模拟实现
与memcpy模拟实现同理,设计一个函数my_memmove,与memmove函数相似
void* my_memmove(void* dest, const void* src, size_t num)
于是我们考虑如何实现my_memmove,既然有重叠,那么就有两种情况,一种是dest指向的内存空间是低地址,src指向的是高地址,另一种则反过来,dest是高地址,src是低地址。
当dest是低地址时,比如dest指向1的地址,src指向3的地址,将3 4 5 6 7 拷贝到1 2 3 4 5的位置上
此时可以用my_memcpy的模拟实现中的从前往后一个一个字节拷贝的方法
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
但是当dest是高地址,src是低地址时,这种方法就不行了,比如,src指向1的地址,dest指向3的地址,把1 2 3 4 5拷贝到3 4 5 6 7上去
由图可知,当把1拷贝到3上去时,放在3位置的3改变为1了,当将3的位置元素拷贝到5的位置时,拷贝的就不是3,而是1,就是错误的结果。那么如何避免这种错误呢,我们可以类比dest是低地址时的方法,是从前向后一个一个字节拷贝,那么dest是高地址的时候,从后向前拷贝不就行了
所以当dest是高地址 src是低地址的时候,dest > src时,从后向前拷贝,
那么实现如下:
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
先判断num是否为0,不为0,先num–;进入循环后,num就是num-1的值了,则*((char*)dest + num)就表示目标空间最后面的内存空间的地址,((char)src + num)就表示被拷贝空间最后面的内存空间的地址,从后向前拷贝
同理主函数如图所示:
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
模拟代码实现如下:
#include <stdio.h>
#include <string.h>
#include <assert.h>
void* my_memmove(void* dest, const void* src, size_t num)
{
assert(dest && src);
void* ret = dest;
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
return ret;
}
int main()
{
int arr1[] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr1 + 2, arr1, 5 * sizeof(int));
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
结果:
三、memset
3.1 memset的使用
memset函数是用来设置内存的,将内存中的值字节为单位设置成想要的值
参数和返回值如下:
void * memset ( void * ptr, int value, size_t num );
ptr是想要改变的内存的起始地址,value是想要设置成的数据,num是想要设置的内存空间的大小
功能:设置从ptr指向的内存空间起始地址开始向后num个字节的内存空间的具体内容
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "hello world";
memset(str, 'x', 6);
printf("%s", str);
return 0;
}
str数组名表示首元素地址,向后6个字节的内存空间内容设置为’x’
结果:
四、memcmp
4.1 memcmp的使用
memcmp函数是比较内存空间大小的一个内存函数,函数参数返回值形式如下:
int memcmp ( const void * ptr1, const void * ptr2, size_t num );
ptr1和ptr2分别是要比较的两个内存空间的起始地址,num是要比较的你存空间的字节大小
功能:⽐较从ptr1和ptr2指针指向的位置开始,向后的num个字节的内存空间中放的具体内容,如果ptr1指向的内容比ptr2指向的内容大,返回一个比0大的数字;如果相等,返回0;如果ptr1指向的内容比ptr2指向的内容小,返回一个比0小的数字(在VS中返回值分别是1, 0,-1)
实现:
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdefg";
char str2[] = "abcdmmm";
int ret =memcmp(str1, str2, 3);
printf("%d", ret);
return 0;
}
str1和str2是数组名,分别代表各自数组首元素的地址,向后比较3个字节内存空间的内容,char类型是一个字节,所以比较三个
结果:
向后比较6个字节,m的ASCII码值比e大
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdefg";
char str2[] = "abcdmmm";
int ret =memcmp(str1, str2, 6);
printf("%d", ret);
return 0;
}
结果:
将str1与str2的内容换一下
#include <stdio.h>
#include <string.h>
int main()
{
char str1[] = "abcdmmm";
char str2[] = "abcdefg";
int ret =memcmp(str1, str2, 6);
printf("%d", ret);
return 0;
}
结果:
如果是整型比较
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 1,2,3,4,3,3 };
int ret =memcmp(arr1, arr2, 16);
printf("%d", ret);
return 0;
}
注意int类型是4个字节,所以注意num的大小;
结果:
但是如果比较了17个字节的话,结果就变为1了
#include <stdio.h>
#include <string.h>
int main()
{
int arr1[] = { 1,2,3,4,5,6 };
int arr2[] = { 1,2,3,4,3,3 };
int ret =memcmp(arr1, arr2, 17);
printf("%d", ret);
return 0;
}
结果:
我们知道正整数再内存中的存储是二进制的,比如1的二进制是
00000000 00000000 00000000 00000001
转换为16进制就是(4位转1位)
0x00 00 00 01
同理5和3的16进制分别是
0x00 00 00 05
0x00 00 00 03
那么如果比较17个字节的话,从左到右先多出来的一位字节不是00吗,其实在内存中是倒着存的
在VS调试窗口中查看arr1数组的内存
可以看到5在内存中先存放的是05 00 00 00
所以先是05,而不是00,05比03大所以返回1.
后面博主也会详细讲解数据在内存中的存储,其中有错误和待改进的地方望大家指出和提出建议不吝赐教,感谢阅读!