1、通过指针详细解释数据在内存中是如何存储的
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int a[5]={0x12345678,0x89ABCDEF,0xAF3D1596,0x424AD841,0x5638ECFB};
unsigned int *p = a;
unsigned char i;
for(i=0; i<sizeof(a)/sizeof(a[0]); i++) //sizeof(a)是数组a的总大小,除以每个数组元素的大小,即得元素个数
{
printf("&a[%d] = 0x%p---->a[%d] = 0x%08X\n",i,&p[i],i,p[i]);
}
return 0;
可以看出数组a中元素在内存中存储方式是从低地址开始的,如下图方式:
- 结论一
上述虽然可以看出数组中的元素在地址空间中的存储方式,但由于每个数组元素都是unsigned int
型,即占四个字节,而每个地址只能放一个字节数据,所以从上述也能看出每个数组元素占用4个地址,那么每个数组元素的四字节数据在连续地址中又是如何存储的呢?下面代码做出解释。
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int a[5]={0x12345678,0x89ABCDEF,0xAF3D1596,0x424AD841,0x5638ECFB};
unsigned int *p = a; //这里p是指向数组元素a[0]的首地址,但通过指针取值(或赋值)时,是直接以a[0]首地址为起点,连续取(赋)四个字节。
unsigned char *q = a; //这里定义q为unsigned char*,q也是指向数组元素a[0]的首地址,通过指针取值(赋值)也是只取(赋)首地址的一个字节数据,这里编译会出警告,因为数组a中的元素都是四字节数据,即每个元素占四个地址,这样的话q+1不会跳到下一个数组元素,只会跳到下一个地址(或者说是下一个字节)。
unsigned char i,j;
for(i=0; i<sizeof(a)/sizeof(a[0]); i++) //sizeof(a)是数组a的总大小,除以每个数组元素的大小,即得元素个数
{
printf("&a[%d] = 0x%p---->a[%d] = 0x%08X\n",i,&p[i],i,p[i]);
}
printf("\n\n\n");
for(i=0; i<sizeof(a)/sizeof(a[0]); i++)
{
for(j=0; j<4; j++)
printf("&a[%d%d] = 0x%p---->a[%d%d] = 0x%X\n",i,j,&q[i*4+j],i,j,q[i*4+j]);
printf("\n");
}
return 0;
}
- 解释编译警告,即下面代码引起的警告:因为&a[0]为
unsigned int*
型,而q为unsigned char *
型,赋值号两边指针类型不同,导致警告。
unsigned int a[5]={0x12345678,0x89ABCDEF,0xAF3D1596,0x424AD841,0x5638ECFB};
unsigned int *p = a;
unsigned char *q = a;
指针变量类型一般和所指向的变量类型一致,就是为了取值和赋值时方便
因为数组a中的数据是4字节数据,即每个数组元素都需要4个地址来存储,正常情况下我们也是定义一个4字节指针变量(这样的指针加一是直接跳4个地址,取值和赋值也是连续取(赋)4个地址的值),来指向存放数组元素的地址,如下图(a)所示;但我们如果想知道每个数组元素在内存中是如何存储的,那么这种指针指向就不能展示出来,上面指针q便是指向单字节的指针变量(这样的指针加一,是指向下一个地址,对它取值或者赋值也是只取(赋)一个地址的值,如下图(b)所示:根据下图及下面的程序输出结果结合便能理解
- 结论二
四字节数据在地址中存储方式为小端模式,即低字节存储在低地址中。
2、指针与地址的区别
指针与地址最大的区别就是,指针可以自增,地址不能自增;可以对指针进行取值,若想对地址(这里指立即数,例0x45FE)进行取值,则必须把地址强转为指针类型再进行取值
- 不同类型指针指向同一变量,然后对指针取值(下面代码编译会出警告,原因上面已解释)
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int b = 0x4567ABCD;
unsigned int *p = &b;
unsigned short int *q = &b;
unsigned char *h =&b;
int i;
printf("&b = 0x%p---->b = 0x%X\n",&b,b);
printf("p = 0x%p---->*p = 0x%X\n",p,*p);
printf("\n");
for(i=0; i<2; i++)
{
printf("q[%d] = 0x%p---->*q[%d] = 0x%X\n",i,(q+i),i,*(q+i));
}
printf("\n");
for(i=0; i<4; i++)
{
printf("h[%d] = 0x%p---->*h[%d] = 0x%X\n",i,(h+i),i,*(h+i));
}
return 0;
}
这里定义了三个指针变量,全部指向变量b,每个指针变量中存储的都是变量b的地址(系统在内存中,为变量分配存储空间的首个字节单元的地址,称之为变量的地址),这里是0x65FDFC
,但由于指针变量p,q,h的类型不同:对指针变量p进行取值或者赋值时,默认以变量b的地址为起始,取或赋连续的4个字节(这里4个字节是因为指针类型为unsigned int*
);同理,对指针变量q进行取值或者赋值时,默认以变量b的地址为起始,取或赋连续的2个字节(这里2个字节是因为指针类型为unsigned short int*
);对指针变量h进行取值或者赋值时,默认以变量b的地址为起始,取或赋连续的1个字节(这里1个字节是因为指针类型为unsigned char*
)。
注:对不同类型指针进行自增运算时,自增一次所跨越的地址数由指针类型决定,如:unsigned int*
类型指针变量,自增一次,跨越的地址数为4;同理unsigned short int*
类型指针变量,自增一次,跨越的地址数为2;等等。
- 将地址强转为指针类型,对其赋新值
#include <stdio.h>
#include <stdlib.h>
int main()
{
unsigned int b = 0x4567ABCD;
int i;
printf("&b = 0x%p---->b = 0x%X\n",&b,b);
printf("&b = 0x%p---->b = 0x%X\n",(unsigned int *)(0x65FE18),*((unsigned int *)(0x65FE18)));
printf("\n");
/**对变量b的4个地址分别写值**/
*((unsigned char*)(0x65FE18)) = 0xEF;
*((unsigned char*)(0x65FE18)+1) = 0x59;//指针加1,地址加1
*((unsigned char*)(0x65FE18)+2) = 0xA8;
*((unsigned char*)(0x65FE18)+3) = 0x7B;
for(i=0;i<4;i++)
{
printf("&b[%d] = 0x%p---->b[%d] = 0x%X\n",i,(unsigned char*)(0x65FE18)+i,i,*((unsigned char*)(0x65FE18)+i));
}
printf("\n");
/**对变量b的4个地址分两次写值(即一次写2个地址)**/
*((unsigned short int*)(0x65FE18)) = 0x254A;
*((unsigned short int*)(0x65FE18)+1) = 0x4C94;//指针加1,地址加2
for(i=0;i<2;i++)
{
printf("&b[%d] = 0x%p---->b[%d] = 0x%X\n",i,(unsigned short int*)(0x65FE18)+i,i,*((unsigned short int*)(0x65FE18)+i));
}
printf("\n");
/**对变量b的4个地址一次写值(即一次写4个地址)**/
*((unsigned int*)(0x65FE18)) = 0xAEF0127D;
printf("&b = 0x%p---->b = 0x%X\n",(unsigned int*)(0x65FE18),*((unsigned int*)(0x65FE18)));
printf("\n");
printf("&b = 0x%p---->b = 0x%X\n",&b,b);
return 0;
}