一个很老的试题稍微往下走一步

时间:2021-02-21 14:39:47

最近刚刚看完primer,灰头土脸的,越看越迟钝,最近又在研究c陷阱与缺陷,看到关于大端(Big-Endian)与小端(Little-Endian)的问题时,忽然想到同学上个月问过的一个笔试问题,于是乎决定死活要整出来,以给自己一一点信心,正好还在犹豫怎么写写自己的primer观后感,先解决这个问题作为预热吧。

问题是求下面程序的输出(假设int型数据占4个字节,且为intel  X86 CPU)

int main(void)
{
int a[5]={1,2,3,4,5};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x,%x",ptr1[-1],*ptr2);
getchar();
return 0;
}
这个问题虽然程序很简短,但是却涉及到数组首地址、地址的偏移、强制类型转换、大小端等问题,初学者解决起来还是要花一定精力的(比如我),其实这个题还考验一个人的认真程度,我一开始就忽略了printf函数已经规定了输出要按照十六进制格式,做的时候还傻乎乎的当成int型的,结果调试的时候就越想越不对劲,一闹脾气,把他们的整数输出也给贴出来了,下面会慢慢说到。

慢慢来分析:

int a[5]={1,2,3,4,5};           //声明了一个数组,包括五个元素
int *ptr1=(int *)(&a+1);

&a,我们先看a,a如果单独拿出来就代表了数组a[5]的首地址,且a的类型为int[5],&a则为取首地址的地址,且&a的类型为int[5]*,这里比较拗口,请朋友们自行去参阅关于指针的知识,并且,这里如果把"&"去掉,你得到的printf的第一个输出绝对不是5,具体为多少,卖个关子,朋友们自行解决。&a+1则为指针的加法运算,+1并不是在我们得到的&a的地址上简单的加一,而是&a所指向的类型的字节数!例如此处,&a指向int[5]类型的a,+1其实是地址在原来的基础上偏移4*5=20个字节!指向了a[5]的末尾元素a[4]的下一个位置(但我们只能读其地址并不能对其解引用得到里面的具体值!),这在c++中是允许的。

继续往下说(int *)(&a+1),众所周知,在一个类型外面加上(),就是把后面的元素强制类型转换,即把&a+1的类型由int[5]*强制转换为int*类型,会出什么事呢?我们看一下输出语句中ptr1[-1],这里相当于*(ptr1 + (-1))即*(ptr1 -1),即ptr1指向的类型然后向前偏移一位,根据后面我们要讲的内存的大小端规则,和上面说的指针的加减法则,可以得知-1实际是偏移了4个字节。最后输出为0x05,也就是我们看到的5。

好了,下面要讲楼主比较痛苦的大小端了,看看这个不厚道的语句int *ptr2=(int *)((int)a+1);
先把a由int[5]很不厚道的强制转换为int型变量,然后对其加一,再把它强制转换为int*类型。

这个问题怎么整呢?最好的办法是打开编译器,一步一步调试的看结果!比如楼主电脑,此时a值为0x0012ff4c,转换为int变成1245004,int值+1为1245005,也就是十六进制的0x0012ff4d,通过强制转换为指针类型也可以得到(int *)((int)a+1)值为0x0012ff4d。可以发现,上式的结果就是使得指针指向了a[0]的第二个字节!这么说不好理解,下面看图:

大端:数据的高优先位对应内存的低地址位

小端:数据的低优先位对应内存的低地址位

例如数据:0x12345678

则大端序为:数据最高优先位的0x12对应内存的最低地址 -> 0x34 -> 0x56 -> 数据的最低优先位0x78对应内存的最高地址

小端序为:数据最低优先位的0x78对应内存的最低地址 -> 0x56-> 0x34-> 数据的最高优先位0x12对应内存的最高地址

由此,可知a[5]={1,2,3,4,5}在小端机中内存的排序为:(按楼主电脑)

一个很老的试题稍微往下走一步一个很老的试题稍微往下走一步一个很老的试题稍微往下走一步

一个很老的试题稍微往下走一步一个很老的试题稍微往下走一步


现在应该能好好的看明白小端排序了吧?

此时的ptr2其实是加了一个整型数值1,相当于在内存中由a[0]的第一个字节指到第二个字节,下图显示了ptr2加一前后的指向:

一个很老的试题稍微往下走一步

那么后面的问题就很简单了,printf("%x,%x",ptr1[-1],*ptr2);,此时ptr2指向了a[0]的00,按照小端原则,指针指向的是内存低地址,其储存的为数据的低优先位,那么按照整形数据存储4个字节来算,其指向的数据变为0x0012ff4d~0x0012ff50,也就是0x02 00 00 00,这也是其输出2 00 00 00的原因了!因为楼主一开始没有注意到printf的%x,而是以为成d%,因此很纠结编译器中*ptr2的值为33554432,这个也很简单,将0x02 00 00 00转换为十进制也就是1*2^25了,有一点需要注意,ptr2+4指向的地址为0x0012ff5d,但是我们的已知内存到了0x0012ff5f也就结束了,所以其所指向的为一个未知数。

楼主其实也是一个大菜鸟,祝大家遇到的问题都能顺利解决,也祝自己能够静下心来的学习c++!