int *ptr2=(int *)((int)a+1);
来源:互联网 发布:前海开源人工智能基金 编辑:程序博客网 时间:2024/05/15 23:43
某日,看到一道比较恶心的C语言的试题,考了很多比较绕的知识点,嘴脸如下:
int main(void)
{
int a[4] = {1, 2, 3, 4};
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int)a+1);
printf("%x, %x/n", ptr1[-1], *ptr2);
return 0;
}
问,在x86平台下输出啥?
题目虽然恶心了点,但作为一个例子来分析,还是挺好玩的。学过C语言的朋友可以暂且不看下文,自己试着分析一下,看看结果跟我的是否一样,也不失为一件趣事。
下面把这道题牵涉到的所有边边角角的问题梳理一遍,详细讨论如下:
1、&a+1
首先明确,a是一个具有4个整型变量的数组的名字,具体地说是这种数组的首元素的首地址,而&a是数组的首地址,请注意措辞。而关于指针加1,则需要指针运算的知识。没学过指针运算或者已经忘记了这个知识点的朋友们,下面就是关于指针运算你需要知道的事实:
就像上面的例子那样,式子&a+1表示的是指针加法运算,而不是普通的数值加法运算,之所以会这样是因为&a是一个指针而非普通数值(虽然它本质上也是一个整数)。那么你会问:加入此时&a=0xFFFF5700,那么&a+1是多少呢?答案是:取决于&a的类型。
a) 如果&a是一个指向char型的指针,那么&a+1 = 0xFFFF5701
b) 如果&a是一个指向short型的指针,那么&a+1 = 0xFFFF5702
c) 如果&a是一个指向int型的指针,那么&a+1 = 0xFFFF5704 (32位机器)
d) 如果&a是一个指向某种结构体struct foo的指针,那么&a+1 = 0xFFFF5700+sizeof(struct foo)
……
还没看出端倪吗?没错,指针加1不是指针内容简单地加1,而是让指针指向下一个数据,加2就是让指针指向下两个数据,这个数据的类型就是指针指向的类型,所以指针的加法究竟会让这个指针指向哪里,取决于这个指针指向的数据类型。
因此,综上所述,当&a与整数1做加法时,实际上是指针的加法,加1的含义是:令指针a指向下一个数据,下一个数据是啥?当然是紧挨着的下一个具有4个整型变量的数组了(因为&a的类型是指向具有4个整型变量的数组的指针嘛),于是a的指向了4的下一个地址,在用此值初始化ptr1,因此ptr1的指向如图所示:
由于在ptr1初始化的时候,令&a+1强制转换成整型指针(关于类型转换的详细讨论请参考http://blog.csdn.net/seton040/archive/2009/10/19/4699869.aspx),因此ptr1[-1]相当于把ptr1往前挪一个整型大小,即4个字节。 如下图:
显然,打印出的第一个数字是a[3]的内容,即数值4.
当然,我们还必须说明一个事实:数组下标是可以为负数的,实际上,取下标符“[ ]”的内部实现,就是指针运算!比如a[2],等价于*(a+2),即以a地址为基址,取偏移量为2的地址的值。所以ptr1[-1]等价于*(ptr-1)。
2、(int *)((int)a+1)
至于指针ptr2的处理更恶心一点,呵呵!它先是把数组名a强制转换成整型变量,然后再加1,然后再强制转换成整型指针!真罗嗦,但不要紧,咱有的是耐性,无非就是让ptr2指向a[0]的第二个字节罢了,此时ptr2指向如下图所示:
显然,此时打印的内容就是ptr2所指向的往后4个字节的内容了,也就是a{0}的后三个字节和a[1]的第一个字节,那究竟会打印出啥玩意儿呢?上面的图没有画出里面的内容,要是把每个字节的内容都画出来就好了,呵呵!
要把上图中每一个字节的内容都打印出来没问题,但是你先要知道字节序的概念,字节序分两种,一种叫大端 字节序(big-endian) ,当然除此之外必然有小端 字节序(little-endian) ,让我们用一个问题,来引出字节序的概念,然后再来搞定这两个小鬼吧!
问题是这样的:对多字节存储的变量,机器是如何做出解释的??请看下图:
假如这是一个普通的int变量,地球人都知道,在32机器上int占用4个字节存储数据,就像上图中显示的那样,在4个字节中放置了一堆数字,但是机器究竟会把这个数解释成0x0103070F呢,还是解释成0x0F070301呢?答案是:都有可能!
官方解释:
1、所谓大端(big-endian)序,就是高优先位对应高有效位。
2、所谓小端(little-endian)序,就是高优先位对应低有效位。
民间解释:
1、所谓大端(big-endian)序,就是读取或者存放数据时,最低位 对应 高地址。
2、所谓小端(big-endian)序,就是读取或者存放数据时,最低位 对应 低地址。
如,要把0x0103070F存放进存储器中时,如果把0E放进高地址处则是小端序,如果把0F放进低地址处则是大端序。照此,上图中存放的数据如果被机器理解成0x0103070F则是该机器是大端序的,否则若被理解成0x0F070301则是小端序的。
回到原来的问题,此时ptr2指向了a[0]的第二个字节。我们以x86平台为例(小端序),此时其内部数据分布是这样的:
由于x86平台是小端序的,根据咱刚刚讨论过的理论,小端序的存取时最低位 对应 低地址,因此将会打印出0200 0000,如果题目中没有说明在x86平台,那答案是不确定的,取决于具体的平台,例如ARM平台就是大端序的。
到此这个试题基本算是圆满解决了,但是我还想再罗嗦一下,关于代码中的那个a,以及那个&a,估计还是有很多朋友心里不怎么平坦吧,每次遇到数组啊指针啊,虽然程序好像仿佛貌似也没出啥毛病,但总感觉是混过去的,这次我们要彻底解决它。记得某人说过,一个真正的程序员,必须对自己写的代码的每一个细节了如指掌。
再来考虑这个定义:
int a[4] = {1, 2, 3, 4};
这时我们必须明确,编译器根据我们提供的类型和数组大小,为我们分配了适当大小的存储区域,并且把这块存储区域叫做a,请注意:&a,就像我们上面提到的,它的类型是“指向具有4个整型变量数组的指针“,简而言之&a是一个数组指针。这个数组的名字a,当它作为右值(简单而言,出现在赋值号“=”左边的就是左值,出现在赋值号“=”右边的就是右值)时代表的是数组首元素的首地址,而不是数组的首地址。此时a的意义跟&a[0]是等价的,都是指向首元素的指针。记住咯!
数组的名字会在另一个地方与指针等价,那就是函数参数表,当出现在函数的参数表里面的时候,不管你写的是数组,还是指针,一旦进入函数内部,通通变成指针。
- int *ptr2=(int *)((int)a+1);
- 一道试题引发的血案 int *ptr2=(int *)((int)a+1)
- int *ptr=(int*)(&a+1);
- int *ptr=(int *)(&a+1)
- int *ptr=(int *)(&a+1)
- int *ptr=(int *)(&a+1)
- int *ptr=(int *)(&a+1)
- int *ptr=(int *)(&a+1)
- int a = (int)(((int*)0)+4);
- int *p=(int *)a;
- int (*a)(int) ; int (*a[10])(int); int *(*a)(int) ;
- int *ptrl=(int*)(&a+1); int *ptr=(int*)((int)a+1) 这两个定义有什么区别
- c++中int a, int *a=new int和 int *a=new int()
- int a 和int*a = new int 的区别
- int a; int* a; int** a; int (*a)[10]; int (*a)(int);int *(*a)[10]区别?
- int a; int* a; int** a; int a[10]; int (*a)[10]; int (*a)(int); 区别
- #include int inc(int a) { return(++a); } int multi(int*a,int*b,int*c) { return(*c=*a**b); }
- int *a[10],int (*a)[10],int *a[10],int (*a)(int),int (*a[10])(int)
- quartz--深入解读Quartz的原理
- 关于 (&a+1) 的困惑
- Windows Azure 安全最佳实践 - 第 3 部分:确定安全框架
- int main(int argc,char* argv[])详解
- JSP/Servlet基础——自定义标签库
- int *ptr2=(int *)((int)a+1);
- 【RDLC】文章汇总
- control_file_record_keep_time设置与RMAN保留策略的关系
- 这个主要是由于tomcat应用服务中有非daemon线程没有关闭。而根据jvm退出规则,只要有非daemon线程存在,jvm就不会退出 。
- 自定义ListView实现底部动画更新item
- Working Practice-保持数据的纯洁性
- c# 操作 xml 文件的 自定义类
- C中的链接属性及作用域
- 背后实力大比拼 探秘七大IT巨头实验室