- memcpy的错误示范
今天在使用memcpy时突发奇想,以前用这个拷贝int,char等类型指针都可以实现,那可不可以实现将char类型指针数据拷贝到结构体/类中呢?
比如定义一个结构体:
struct Test
{
char c1;
int i1;
char c2;
}test;
再定义一个char类型指针,由于Test结构体中有2个char类型(2*1个字节),1个int类型(4个字节),所以开辟6个字节的内存空间:
char *c = (char*)malloc(6);
再给char指针指向的内存赋值,我将char指针首地址至首地址+5所指向的内存空间赋值:
*c = 1;
*(c+1) = 2;
*(c+2) = 3;
*(c+3) = 4;
*(c+4) = 5;
*(c+5) = 6;
再通过memcpy拷贝一下内存:
memcpy(&test,c,6);
原本想着是test.c1 = *c = 1,test.i1 = *(c+1)<<24+*(c+2)<<16+*(c+3)<<8+*(c+4) = 33752069,test.c2 = *(c+5) = 6。可是最后结果悲剧了,以下是运行结果:
这是什么原因导致的呢?下面开始讲解今日的收获——内存对齐。
- 内存对齐
有良好编程习惯的小伙伴都会使用sizeof(类型)来获得类型的内存大小,我们先来看一下test的内存长度,没错,是12!与我们之前想的6个字节有着很大的差距。
原来编译器在分配内存是并不是单纯的"你定义一个类型,内存地址往上加一个类型长度单位",而是存在一个内存对齐的概念。下图中test.c1地址为0x3af688,test.i1地址为0x3af68c,test.c2的地址为0x3af690。c1和i1的地址空间跨越了4个字节,i1和c2跨越了4个字节。
可以这么理解,假设4个字节单位是一个房间,而类型是一个人,char是一个瘦子,int是一个大胖子。这个大胖子太胖了,自己住一个房间,别人根本挤不进去。而有的同学就在想了,那两个瘦子(char)可以住一个房间么?伴随着这个想法,我们改一下结构体的定义,再运行看下分配内存地址:
struct Test
{
char c1;
char c2;
int i1;
}test;
原来编译器在分配内存的时候,按照定义顺序分配内存地址(房间)。先定义先分配,后定义后分配。编译器会先看该类型能不能住的下当前房间,住得下往里塞,住不下开另一个房间。一般房间有4张床,而数据类型大小为1,2,4,8(x64)个字节,除了8个字节的“超级大胖子”,需要凿开一面墙,为他开两个房间,睡8张床之外,其它类型数据最多只需要住1个房间。
值得一提的是,房间首地址都能被4整除,short等2个字节长度的类型所开辟的内存首地址能被2整除。也就是说在char类型后面紧接着定义一个short,它的床不一定紧挨这char,而是看当前床地址是双数还是单数,双数给short住,单数不给住。若当前房间没有双数的床,那么就移至下一个房间,住双数编号的床。
以上就是所谓的内存对齐的内容。