1.memmove函数
首先,对于memmove函数,我们应该首先做一个了解。什么是memmove函数呢?在此之前还需要介绍一个函数,就是memcpy。memcpy的作用就是对内存进行拷贝,但如果是在重叠内存的情况下,memcpy对于其内存拷贝就很有可能发生问题。那么这个时候,我们就可以考虑使用memmove函数来对重叠内存拷贝的一个处理。
首先来简单了解一下这个memmove函数。
英文太长我就不读了,简单来说,这个函数有三个参数,第一个是将要拷贝到的地方,第二个是将要被拷贝的数据,第三个是要被拷贝数据的字节大小。注意!第三个的是字节大小,而不是个数大小。也就意味着,如果在一个数组里面,不同数组类型的数据的字节数是不一样的。也就意味着传入的字节数是不一样的。如果在int类型的数组里面,你想要使用这个函数来拷贝20个字节的数据,那对于int类型的数组来说,就是拷贝了5个元素;而对于char类型的数组来说,则是拷贝了20个元素。到现在为止还是太过于抽象,下面上代码
#include <stdio.h>
#include <string.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
memmove(arr + 1, arr, 20);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
在这里我以int类型的数组为例,这段代码执行的操作是将从arr开始的位置到第20个字节拷贝到从arr+1开始的位置到第20个字节的位置,感觉又抽象了,那就这么说,就是把1,2,3,4,5的数据拷贝到2,3,4,5,6的数据。下面来看看结果
可以看到代码很好的把结果运行了出来。
1.memmove函数的模拟实现
既然在上面已经很好地了解到memmove函数的用法,那么我们能否试试将memmove函数进行模拟实现出来呢?
首先我们要弄清楚这个函数是怎么拷贝的。
如果我想把这个图中的3,4,5,6,7拷贝到1,2,3,4,5中,可以发现,3,4,5,6,7所在的绿色框是在1,2,3,4,5所在的红色框的后面,为了保证数据不会丢失,我们可以从前往后拷贝,什么意思呢?就是说,如果是从7->3这样子拷贝的话,7首先就会被拷贝到5的位置,6会被拷贝到4的位置,这样就会导致4,5这两个数据的丢失。而如果是按3->7这样子拷贝的话,3就首先会被拷贝到1的位置,4就会被拷贝到2的位置,这样以此类推,就不会出现数据丢失的情况。
那如果我想把这个1,2,3,4,5拷贝到3,4,5,6,7这个位置呢?可以发现,这个情况跟上面的那个情况完全相反,因此,这个地方可以采用从后向前拷贝,也就是拷贝顺序是5->1的。这样才不会出现数据丢失的情况。
都出现了两种情况了,那是不是还有第三种情况?有的,第三种情况就是这两个框刚好不重叠,也就是说,如果我想把1,2,3,4,5的数据拷贝到6,7,8,9,10里面,那么从前往后和从后往前的拷贝顺序都是一样的。二选一即可。
最后来总结一下,如果发现拷贝的目的地在数据前面,那么拷贝顺序就是从前往后拷贝;如果拷贝的目的地在要拷贝数据的后面,那么拷贝顺序就是从后往前拷贝。
分析了这么多,代码的实现也可以分两种情况讨论。
1.当要拷贝的数据在目的地的后面,也就是说目的地在要被拷贝数据的前面时。
if (dest < src)
{
while (num--)
{
*(char*)dest = *(char*)src;
dest = (char*)dest + 1;
src = (char*)src + 1;
}
}
这个代码里面有两个需要注意的点,第一个是dest<src是什么?第二个是为什么dest=(char*)dest+1?为什么不能写成dest+=dest?别急,且听我慢慢道来~
我们在上面已经把将会出现的情况分成了两种,而这个代码就是属于第一种情况:当目的地在要拷贝数据的前面时。那为什么这种情况要写成dest<src呢?首先,在内存种,由于内存是一块连续的空间,所以内存是从低地址到高地址的。
而我们传过来的void*dest和void*src又全都是指针数据,都是地址,所以比较的也是地址,因此如果发现dest<src,那么就说明dest在低地址位,src在高地址位,也可以证明dest在src的前面。
那第二个是为什么dest=(char*)dest+1?为什么不能写成dest+=dest?
在while循环里,由于不知道传输过来的数据是什么类型的数据,在想进行数据拷贝时,我们可以把数据强制转换成char*来进行拷贝,因为char*大小占一个字节,一个字节一个字节来拷贝是最安全的。如果dest直接写成dest++的话,那么跳过的就不一定会是一个字节的大小了,只有把他强转成char类型,才会跳过一个字节。
2.当要拷贝的数据在目的地的前面,也就是说目的地在要被拷贝数据的后面时。
这个则是从后向前拷贝,先拷贝后面的数据,拷贝顺序是后面的数据->前面的数据
else
{
while (num--)
{
*((char*)dest + num) = *((char*)src + num);
}
}
*((char*)dest + num)代表着强转成char类型之后,+num的数组下标,而num是从20开始,因此则符合从后往前传的想法。
整体代码如下
#include <stdio.h>
void* my_memmove(void* dest, void* src, size_t num)
{
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);
}
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
my_memmove(arr + 2, arr, 20);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行结果
2.数据在内存中的存储(整形)
1.原码,反码,补码
在二进制中,一个整形数据所占的字节是4个字节,1个字节等于8个比特位,而4个字节就等于32个比特位。左边的第一个是符号位,如果符号位是1,则表示为负数;如果符号位是0,则表示为正数。正数的原码,反码和补码都是一样的;而负数与正数不同,负数的反码是由原码的符号位不变,其他位按位取反得到,补码是由反码+1得到,补码得到原码也是按位取反+1。
2.大端存储和小端存储
在每一台机器中,都有自己独特的存储方式,而大多数的电脑中,存储的方式都是小端村粗。那么什么是小端存储呢?
如果我定义一个int a=0x 11 22 33 44则从左往右,11是高位字节,44是低位字节。而在前面说过,地址从右往左是由低到高的,那么就可以得出,小端存储方式就是就是将低位字节内容存储到低地址处,高位字节存储到高地址处,而大端存储方式则是将低位字节内容存储到高位地址处,高位字节内容存储到地位字节处。
那么,我们能不能写出一个程序来判断我们的机器是那种存储方式呢?其实思路很简单,定义一个int a=1,那么这个1在内存中的存储要么是00 00 00 01,要么是01 00 00 00,我们只要判断第一个字节是否是1就可以了。那怎么才能把一个完整的数据"拆开"变成一个个字节的形式呢?我们首先如果想把他变成一个个字节的话,强转成char*类型是必不可少的。因此,我们可以写成
*((char*)(&a))取出来第一个字节,判断是否为1,如果是1,就证明他是低位字节存储到低地址中,是小端存储,否则就是高位存储。思路有了,代码走起~
#include <stdio.h>
int main()
{
int i = 1;
if (*(char*)(&i) = 1)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}