大家都知道,整数要用int,字符要用char,那么是否明白数据在内存中的表现是什么样,类、指针到底是什么东东,动态分配到底怎么回事呢?
我们一个一个来,先说数据在内存中的存储,内存是什么东东呢?你可以把它当成是一个房间,内存中的内容就是放在房间中的东西,不管它们是什么东西,而数据类型就是对存放在房间内的东西的归类后的类型。房间的大小是固定的,每一类东西所占用的房间空间也是固定的。我们假设这个特殊房间的基本存储单位是字节(Byte),那么每一种数据类型的存储消耗是什么样的呢?
最常用的就是char,short,int,long,它们分别是占用1,2,4,4个字节,特别要提一下这个int,这是一个比较特殊的类型,大家发现int和long都是一样的,为什么还有两种类型呢?int占用4个字节是因为现在的系统都是32位机,包括嵌入式开发的设备现在也大部分是32位机,int是一个与机器甚至编译器相关的一种类型,这也提供了程序的兼容性。要注意的是基本数据类型就那么几种,像C++标准库里的很多如queue、string等已经是类了,包括开发环境也会提供很多的封装,但那么都是类或结构。
可能有人会问:那unsigned 这个关键字呢,也就是有符号与无符号占用的空间是什么样的呢?其实呀,有符号数与无符号数占用的空间是一样的。比如说char与unsigned char都是占用1个字节,它们的区别是,若定义成有符号,则所占用空间的最高位用来表示符号,0为正,1为负。也正因为如此,char表示的范围是-127~127,而unsigned char则是0~255。再换种说法,因为定义成有符号,最高位被用于识别符号,也就是有效的数据位只有7位,也就是最大只能表示127,加上符号那就是它的范围-127~127了,那么无符号也类似,8位有效的位最大可以表示255,所以范围就是0~255。
结构、类在存储上可以理解为同类,就是把一些基本类型的数据组合起来当一个整体来看,只是类多了一些函数而已,至于函数的存储以后再详说。所以结构的大小就是组合数据中基本数据的总和,比如:结构中包含三个long,一个char数组10个成员,那么这个结构的占用的空间就是4*3+1*10=22Bytes。这就像你放一个机器人进房间,但这个机器人其实是由多个基本零件组成的,所以它占用的空间就是所有零件占用空间的总和。要注意的是在嵌入式开发中,有一个字节对齐的问题,就是当你的数据不足一定的字节数时,编译器会帮你补齐,具体的补齐方法及字节数应该参考不同MPU的Datasheet。
指针,所有学习C/C++的人都头疼的事,想当初也因为它的难而打了退堂鼓。我们现在知道不同的数据类型占用的空间是不一样的,那指针呢,还是以房间为似吧。我们现在把房间以字节为单位都分割好,并编号1~50,也就是假设现在的内存只有50个字节可分配。我们定义了一个整形变量Var,那么它占用四个字节,也就是编号1~4这四个空间是Var变量用来存储值的地方。那么1~50这些数字我们把它们叫做地址,就是1~50这50个地址的空间可以存放东西。如果Var的值是800,对应的16进制是0x320,1~4这四个地址空间是怎么分配数据的呢,以低端在前为例,1号地址存的是0x20,2号地址存的是0x03,3号地址存的是0x00,4号地址存的是0x00。指针,就是地址,定义一个整形指针,把Var取址赋给它,int *p = &Var; 那么p的值是多少?就是1,因为Var的地址就是从1开始,定义了p也就是定义了一个指向1地址的整形指针。
不同类型的指针,其实都是地址,加入类型是为了告诉编译器读写数据时操作的字节数。如下面的代码,程序执行完后,Var的值是多少?(值是768,为什么?)
int Var = 800;
char *p = (char*)&Var;
*p = 0;
看下下面的图就明白了
因为p定义的是char指针,所以只操作1个字节,如果把char *p = (char*)&Var;改成int *p = &Var;那么Var的值会变为0
上面的例子用到了强制转换,这个我们后面再谈,我们继续指针。
地址不会像上面那样编号从1开始,一般都不小,当然这也只是一个数字,现在的系统基本上都是32、64位,也就对应寻址的最大地址分别就是32位和64位。
指针变量也是变量,也是要占用一定的空间,根据上面所述,32位系统下,不管什么类型的指针都是占用32位,也就是4个字节,和int,long一样,
其实一层的指针对很多人来说都还好理解,最难的是在多层指针,我们来看多层指针的例子:
int Var = 800;
int *p = &Var;
int **pp = &p;
上面各个变量的值是什么样的,请看下图:Var的值为0x00000320(800),地址(&Var)为0x00000001,将p指向Var就是把Var的地址做为p的值存储在p的空间里;也就是这时候的p的值为0x00000001,地址(&p)为0x00000007;pp也是类似的,将pp指向p就是把p的地址做为值存储在pp的空间里;也就是这时候的pp的值为0x00000007,地址(&pp)为0x00000011(17的16进制值),如有更多层次的指针就以此类推。
如果你把指针搞清楚了,其实它就是内存空间与地址,那么接下来的动态分配内存就好理解了。
动态分配就是在程序运行的时候根据需要申请一定的空间用于数据的读写,像数据类型是在写代码的时候就已经固定了其大小,而有时候我们往往程序运行的时候,根据实际情况,当下所要的空间不是一定的。比如说,打开文件将文件的内容读取出来,可是每个文件它的文件大小是不一样的,也就是说这时候我们需要申请的内存大小是不一样的。
在C最常用的就是malloc和free两个函数来动态申请内存,而且这两个函数必须配对使用,在C++,大家已经习惯于用new和delete这两个关键字了,一样配对使用。
其实基本上编译器的new和delete这两个关键字内部的实现也是调用malloc和free的。
大家也注意到了,不管什么样的指针占用的空间是一样的,所谓的不同类型的指针只是声明指针所指数据读写的范围,所以malloc出来的就是空指针,也就是未定义类型的指针,也可以把这段内存当作任何类型的指针来用。
总结一下吧,其实数据类型主要要搞清楚的是内存、地址之间的关系,只要搞清楚了,什么的数据都可以把它当内存和地址来看。
大家来看一下下面这些语句它们的执行过程和结果是什么样的
int Var = 800;
int *p = (int*)malloc(sizeof(int));
*p=33;
char cp = (char *)p;
Var = &p;
free(p);
p = &Var;
*p = 32;