一、指针的内存布局
int *p;
这里定义了一个指针p。p到底是什么?p占用的空间是多大?我在Linux上用sizeof测试了一下(32位系统):sizeof(p)的值为4,当然此处恰巧int在32位系统上也是占用4个字节,不过int*并不就是int类型的大小,只是int*也刚刚好是4个字节罢了。此处的指针p就是在内存上占用4个字节(32位系统),然后把这4个字节大小的空间命名为p,同时限定的这4个字节的空间里只能存储某个地址,即使存入任何其他的数据,也会被当成地址来处理。而且这4个字节只能存放某个int型的数据。
如上图所示,我们把 p 称为指针变量,p 里存储的内存地址处的内存称为 p 所指向的内存。指针变量 p 里存储的任何数据都将被当作地址来处理。
一个基本的数据类型(包括结构体等自定义类型)加上“*”号就构成了一个指针类型。这个指针类型的大小是一定的,与“*”号前面的数据类型无关。“*”号前面的数据类型只是说明指针所指向的内存里存储的数据类型。所以,在 32 位系统下,不管什么样的指针类型,其大小都为 4byte。可以测试一下 sizeof(void *)。
二、int *p = NULL 和*p = NULL的区别
int *p = NULL;
这时候通过gdb调试 p 的值为 0x00000000。这句代码的意思是:定义一个指针变量 p,其指向的内存里面保存的是 int 类型的数据;在定义变量 p 的同时把 p 的值设置为0x00000000,而不是把*p 的值设置为 0x00000000。这个过程叫做初始化,是在编译的时候进行的。
再看下面的代码:
int *p;
*p = NULL;
用gdb调试这两行代码。第一行代码,定义了一个指针变量 p,其指向的内存里面保存的是 int 类型的数据;但是这时候变量 p 本身的值是多少不得而知,也就是说现在变量 p 保存的有可能是一个非法的地址。第二行代码,给*p 赋值为 NULL,即给 p指向的内存赋值为 NULL;但是由于 p 指向的内存可能是非法的,所以调试的时候编译器可能会报告一个内存访问错误。这样的话,我们可以把上面的代码改写改写,使 p 指向一块合法的内存:
int i = 10;
int *p = &i;
*p = NULL;
在编译器上调试一下,我们发现 p 指向的内存由原来的 10 变为 0 了;而 p 本身的值, 即内存地址并没有改变。
这里的NULL在编译器里面被宏定义为0:
#define NULL 0
三、数组的内存布局
int a[5];
这里定义了一个数组,其包含了 5 个 int 型的数据。我们可以用 a[0],a[1]等来访问数组里面的每一个元素,那么这些元素的名字就是 a[0],a[1]…吗?请看下面示意图:
如上图所示,当我们定义一个数组 a 时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为 a。名字 a 一旦与这块内存匹配就不能被改变。 a[0],a[1]等为 a 的元素,但并非元素的名字。数组的每一个元素都是没有名字的。
sizeof(a)的值为 sizeof(int)*5, 32 位系统下为 20。
sizeof(a[0])的值为 sizeof(int), 32 位系统下为 4。
sizeof(a[5])的值为 sizeof(int), 32 位系统下为 4。
sizeof(a[5])的值在 32 位系统下为 4。并没有出错,为什么呢?因为sizeof 是关键字不是函数。函数求值是在运行的时候,而关键字 sizeof 求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问 a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用 a[5]并不会出错。sizeof(&a[0])的值在 32 位系下为 4,这很好理解。取元素 a[0]的首地址。
四、&a[0]和&a 的区别
这里&a[0]和&a 到底有什么区别呢? a[0]是一个元素, a 是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。请看如下示例程序:
main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
对指针进行加 1 操作,得到的是下一个元素的地址,而不是原有地址值直接加 1。所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 因此,对上题来说, a 是一个一维数组,数组中有 5 个元素; ptr 是一个 int 型的指针。&a + 1: 取数组 a 的首地址,该地址的值加上 sizeof(a) 的值,即 &a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。(int *)(&a+1): 则是把上一步计算出来的地址,强制转换为 int * 类型,赋值给 ptr。*(a+1): a,&a 的值是一样的,但意思不一样, a 是数组首元素的首地址,也就是 a[0]的首地址, &a 是数组的首地址, a+1 是数组下一元素的首地址,即 a[1]的首地址,&a+1 是下一个数组的首地址。所以输出 2。*(ptr-1): 因为 ptr 是指向 a[5],并且 ptr 是 int * 类型,所以 *(ptr-1) 是指向 a[4] ,输出 5。
五、指针和数组的关系
指针就是指针,指针变量在 32 位系统下,永远占 4 个 byte,其值为某一个内存的地址。指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型和个数。数组可以存任何类型的数据,但不能存函数。
更多的精彩内容请关注微信公众号“Linux嵌入式”