谈内存之前,首先了解一下C++的类型在不同编译中的所占字节:
1.字节和字长
字节,八位就是一个字节,是固定概念。字长是指计算机一次能处理的二进制数据的长度,是一个非固定的概念。例如,8位计算机的字长为8bit,即一个字节, 32位计算机的字长位32bit,即4个字节,同理,64位计算机的字长为64bit,即8字节。
2.char类型始终是一个字节,即8bit。
3.int、short int和long int
通常int占4个字节,short占2个字节,long占4个字节或8个字节(在32位机器中为4个字节)。
4.浮点型float、双精度double、和长双精度long double
典型情况下,float 为4个字节,double是8个字节,long double为12个字节或16个字节。
C/C++仅仅定义了这些基本数据类型之间的关系,并没有定义严格定义它们的字长。
在不同的平台上,根据编译器不同的实现,它们的字长如下表所示:
数据类型 | LP64 | ILP64 | LLP64 | ILP32 | LP32 |
char | 8 | 8 | 8 | 8 | 8 |
short | 16 | 16 | 16 | 16 | 16 |
_int32 | N/A | 32 | N/A | N/A | N/A |
int | 32 | 64 | 32 | 32 | 16 |
long | 64 | 64 | 32 | 32 | 32 |
long long | N/A | N/A | 64 | N/A | N/A |
pointer | 64 | 64 | 64 | 32 | 32 |
在这张表中,LP64,ILP64,LLP64是64位平台上的字长模型,ILP32和LP32是32位平台上的字长模型。
LP64意思是long和pointer是64位,
ILP64指 int,long,pointer是64位,
LLP指long long和pointer是32-bit的。
ILP32指int,long和pointer是32位的,
LP32指long和pointer是32位的。
32位Windows采用的是ILP32数据模型,64位Windows采用的是LLP64数据模型。
所以,Windows上的32位程序设计和64位程序设计最大的不同(也就是ILP32和LLP64的不同),就在于指针的长度不同??由32位变成了64位。
Win32 API在很多情况下,都需要将整数转换成指针或者相反。在 32 位的硬件上不会有问题,其中指针的大小和整数的大小是相同的,但在 64 位的硬件上却完全不一样。
1.变量在内存中
测试用例:
#include <iostream>
using namespace std;
int main(void)
{
/*栈空间*/
int a = 1;
int b = 2;
int n1[2] = {1,2};
int n2[2][2] = {1,2,3,4};
int *a1 = &a;
int *n = n1;
/*堆空间*/
int *n3 = new int[];
int *n4 = new int[2];
return 0;
}
(1) 由内存列表可以看出:
变量在内存中从高地址向低地址生长,说明存储在栈中
(2) 由红色标志的地址可以看出:
数组本身就是一个地址,所以数组名与取地址的数组名所表示的地址是相同的
(3) 由绿色方框中的地址可以看出:
内存地址是从低地址向高地址生长,说明用new申请的内存是在堆中.
变量及其申请的空间在内存中的具体存储:
每个变量后都有8字节的cc作为间隔.
2.指针在内存中
指针的大小用sizeof可以获取.
指针的大小跟编译器有关,
例如:
用VS2013中的32位编译器编译指针,sizeof(指针)=4
用VS2013中的64位编译器编译指针,sizeof(指针)=8
vs2013编译器调整:
3.复杂指针
复杂指针阅读准则:
①阅读变量时:
“右左法则”是一个简单的法则,但能让你准确理解所有的声明。这个法则运用如下:从最内部的括号开始阅读声明,向右看,然后向左看。当你碰到一个括号时就调转阅读的方向。括号内的所有内容都分析完毕就跳出括号的范围。这样继续,直到整个声明都被分析完毕。
对上述“右左法则”做一个小小的修正:当你第一次开始阅读声明的时候,你必须从变量名开始,而不是从最内部的括号。
②阅读函数指针时:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。
在上述程序中添加如下指针:
/*指针*/
//数组指针(本质上是一个指针,而且是一个指向数组的指针)
int (*n5)[2]=n2;
//n5 = n2;
//n5 = &n1;
//指针数组(本质上是一个数组,里面存放的都是指针)
int *n6[2]; //int *(n6[2]) //[]的优先级比*高
n6[0] = n1;
n6[1] = &a;
内存分布: (由于重新编译程序,内存重新分配,所以地址与上面有所不同,这里不想再重新做图了,偷懒了,知道具体分布就ok了)
由此验证指针数组与数组指针的区别.
数组指针:本质上是一个指针,而且是一个指向数组的指针
指针数组:本质上是一个数组,里面存放的都是指针
数组指针:
int (*a)[2]
[2] 用来控制申请内存的长度为2.
(*a)用来控制标识内存的地址,且没有限制.
总结: 相当于限制了申请内存的长度,地址的个数没限制
指针数组:
int *p[4] 相当于int *(p[4]) ,因为[]的优先级比*高.
(p[4]) 相当于限制了申请地址的个数,只能为4.
但是每一个地址所指向的内存是可变的(即内存长度不定).
总结: 相当于限制了申请地址的个数,内存的的长度没有限制