指针运算探究

 

有以下程序:

 

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	int *p = arr;
	*p = 10;
	p++;
	*p = 20;
	printf("十进制 %d,%d\n",arr[0],arr[1]);
    printf("十六进制 %08x,%08x\n",arr[0],arr[1]);
	return 0;
}

我们暂时先不要去考虑其答案,而是先来探究中间这个“p++”在这里是什么意思?

在该程序中,我们定义了一个整型数组,然后定义了一个整形指针变量,其指向的是该数组的首元素地址,所以之后对其解引用“*p=10”,自然会使arr[0]=10,这是没有问题的。之后重点来了,这里的“p++”到底给p加了个什么东西?我们有以下猜想:

1.    加了整个数组的长度?

2.    加了一个字节的长度?

3.    加了四个字节的长度?

首先我们很容易把 1否决,因为这是不可能的,如果一下子给P加整个数组的长度,p就会从数组的最前端直接跳到末尾,这种结果对我们来说是毫无意义的。

那么 2 就有点意思了,加一个字节的长度会发生什么呢?

我们知道,整形数组中,每个元素需要用四个字节来保存,我们用下面的图像来简略表示arr[0]= 10和arr[1] = 2

 

其中,我们用红色字体假设其地址,每个格子所占空间为四个字节。接下来我们把这个图片放大四倍,即每个格子所占空间变为一个字节。如图:

之后我们便需要把10和2分别放入四个格子中,这其实很简单。

例如10,由于是四个格子,每个格子一个字节,即要用32位表示10 ,但每个格子写8个数字太麻烦了,所以我们用十六进制 表示10,这样刚好每两个十六进制数字放一个格子,我们知道10的十六进制是00 00 00 0a . 同理得出 2 的十六进制 00 00 00 02 .

 

最后重要的来了,是不是直接把这八对十六进制数字按次序放进去呢?当然不是!因为存在大小端的问题!低地址放大数据是大端,低地址放小数据是小端,而我们的PC正好是小端,所以要在低地址处放入小的数据。那么0x0000000a和0x00000002应该怎么放呢?如下图:

也许有人不明白,举个例子:十进制的10007中1和7 谁大?你可能会说 7 大,但你不能只看数字而不看位数啊,7是7,可这里的1是10000啊,所以如果也要把10007按小端储存,就应该把 7 放到最前面 ,1放到最后面。所以你现在明白为甚么上面的十六进制是为什么这么储存的了吧?

然后我们继续,让一级指针 p 加一个字节,那么解引用后赋予的值应该在下图绿框的地方,

即我们要把20放入绿框中,有了上面的经验,步骤就很清晰了,首先,20的十六进制是00 00 00 14,然后按照小端低地址放小数据的规则,有以下结果:

 

看到这里,想必大家已经知道,“加一个字节”这个猜想是绝对站不住脚的,“p++”绝对不可能整出这么复杂的玩意儿出来,只加一个字节,只会使指针错位。不过,既然算到了这里,我们就来看看这真正只加一个字节会对结果产生什么影响,接下来的程序如下:

 

int main()
{
	int arr[] = {1,2,3,4,5,6,7,8,9,10};
	int *p = arr;
	*p = 10;
	p = (int *)((int)p+1); //该语句是让 p 只加一个字节
	*p = 20;
	printf("%08x,%08x\n",arr[0],arr[1]); //输出arr[0] 、arr[1] 的八位十六进制

	return 0;
}

其输出结果为0000140a,00000000

别惊讶为甚么是反的…..因为你储存是按照小端储存,输出是正常的啊….

该结果跟我们的计算结果是一样的,有的同学还想知道其十进制的值,只要把上面输出语句里的“%08x”改为“%d”即可,其输出结果是5130,0

这里就可以更直观地看出其不靠谱了,10和2怎么可能变成这么大的数字?

那么最后,我们再回过头看本篇文章开头的那道程序,结果我就在这里公布了

十进制  10,20

十六进制0000000a,00000014

 

看到这两个十六进制数字,是不是感到莫名亲切?

 

 

 

总结:指针与数字间的加法,需要进行调整,其调整权重为指针所指向的类型长度,如:上面的例子,其权重就为sizeof(int)

           总的计算式为:指针+(数字 * 权重)。

          指针与数字间的减法,与指针加法类似,在此不赘述。其总的计算式为:指针 -(数字 * 权重)

 

接下来,我们讨论指针与指针之间的运算。

 

首先,我们需要知道的是,每一个运算都有其意义,例如:指针和一个数字进行加减运算,其意义是改变了指针指向的地址,使其严格指向下一个数据。而我们现在需要考虑指针之间的运算是什么含义?


我们先看 “指针+指针” ,这个运算有意义吗?显然是没有的,我们拿时间来作比喻,下午三点加下午四点得出来的值是什么?你肯定会是七,但这个值有意义吗?就算你说这个七是七点的意思,但时钟里每一个时刻都是独立的,两个时刻相加,你只是把他们的值加起来,就其本身而言并无意义。所以指针之间不存在加法运算。

 

然后我们看 “指针-指针”,还是拿时间来举例子,下午七点,减去下午三点,得出来的值是什么含义? 答案是四。你能说这代表的是四点这个时刻吗?并不是,这个四代表的是,三点和七点之间间隔着四个小时。

 

到这里指针之间减法的意义就呼之欲出了,两个指针相减得出的应该是两者之间元素的个数。


指针之间相减其计算式为:(指针 - 指针)/权重


下面是关于指针运算的程序,仅作为练习题,其结果已写明,请自行对照。

 

 

 

指针 + 数字:

 

int main()
{
	int *p = (int *)2000;
	printf("%d\n",p+4);//2016
	printf("%d\n",(short *)p+4);//2008
	printf("%d\n",(double *)p+4);//2032
	printf("%d\n",(float **)p+4);//2016
	printf("%d\n",(unsigned short *)p+4);//2008
	printf("%d\n",(long *)p+4);//2016
	printf("%d\n",(char *)p+4);//2004
	printf("%d\n",(unsigned long long)p+4);//2004
	return 0;
}

指针 - 数字:

int main()
{
	int *p = (int *)0x2010;
	printf("%x\n",p-2);//2008
	printf("%x\n",(short *)p-2);//200c
	printf("%x\n",(unsigned long *)p-2);//2008
	printf("%x\n",(long long **)p-2);//2008
	printf("%x\n",(float *)p-2);//2008
	printf("%x\n",(double *)p-2);//2000
	printf("%x\n",(char *)p-2);//200e
	printf("%x\n",(unsigned long )p-2);//200e

	return 0;
}

 

指针 - 指针:

int main()
{
	int arr[10] = {1};
	int *p = &arr[1];
	int *q = &arr[9];
	printf("%d\n",p-q);//-8
	printf("%d\n",q-p);//8
	printf("%d\n",(short *)q-(short *)p);//16
	printf("%d\n",(double *)q-(double *)p);//4
	printf("%d\n",(int ***)q-(int ***)p);//8
	printf("%d\n",(char **)q-(char **)p);//8
	printf("%d\n",(long)q-(long)p);//32

	return 0;
}

其结果皆为标准答案,若不符请仔细思考,请特别注意*的存在与否以及各类型的字节数!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值