数组 指针 内存 布局 大小端 汇编 c语言

下面的C代码在VC++6.0下的运行结果是什么?请详细说明原因。
#include <stdio.h>
int main()
{
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);
return 0;
}

 

 

学到了新知识
但也受到了极大的打击
将楼上各位牛人的经典解答引用总结一下,已备不时之需:

/*
答案:
ptr1[-1]= 5   
  *ptr2 = 2000000

*/


/*mask 1*/
int main()  
{  
  int a[5]={1,2,3,4,5};  
  int *ptr1=(int *)(&a+1);
  // a本身就为一个数组的地址,其值=&a[0],即数组第一个元素的地址,对a再取址
  // &a = a,似乎没什么不同,其实这里发生了对齐变更,a+1的对齐是以int为界的
  // 而&a+1是以整个数组为单位的,即5个int=20字节,这是关键!!!
  // 那么&a+1实际是跨越了整个数组,可得ptr1存储了数组a最后一个元素5紧跟着的一个int的地址

  int *ptr2=(int *)((int )a+1);  
  // 同理,(int)a先将其值(地址)转为整型值,再加一成为一个新的整数
  // (int *)将这个值转换为一个指向int型变量的指针(地址)
  // ptr2即存储了这个地址和指向的变量的类型

  printf("%x,%x",ptr1[-1],*ptr2);  
  // 先解释一下a[-1],即将数组向前偏差一个类型大小的距离
  // 数组a的内存布局(16进制):01000000 02000000 03000000 04000000 05000000
  // ptr1[-1]
  // 你说这里等于什么呢?数组a最后一个元素(5)紧跟着一个int的前面一个元素当然就是5了。
  // *ptr2
  // ptr2的值为a保存的地址值的绝对值+1,假设a=1000,那么(int)a+1=1001。
  // 而后将其再转为(int *)指针,那么指针指向的内存布局为000000 02,即向后移了一个字节
  // 那么在little-enddian架构下显示出来为02000000,低位在高地址。

  return 0;  
}


/*mask 2*/
/*
&a 可以看作是a[5]类型的指针,所以&a+1指向下一个a[5]数组的地址,
也就是说(int*)(&a+1) = (int)a+sizeof(a);  

下面是测试代码:  

#include "stdio.h"
int main()
{   
  int a[] = { 1, 2, 3, 4, 5};
  printf("a = 0x%x/n", a);
  printf("&a = 0x%x/n", &a);
  printf("&a[4]+1 = 0x%x/n", &a[4]+1);
  printf("(int *)(&a+1) = 0x%x/n", (int *)(&a+1));
  printf("(int)a+sizeof(a) = 0x%x/n", (int)a+sizeof(a));
  return 0;
}


运行结果如下:  
a = 0x12ff6c  
&a = 0x12ff6c  
&a[4]+1 = 0x12ff80  
(int *)(&a+1) = 0x12ff80  
(int)a+sizeof(a) = 0x12ff80  
Press any key to continue  

楼主出这种题就是钻牛角尖。。
*/


/*
mark 3:


楼主这个题确定值得思考一下.  
查看一下内存分布情况:  

C/C++ code
int a[5]={1,2,3,4,5};  
则程序的内存分配(即高地址在前低地址在后)为
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
/|/
 |
a   



第一点:  

C/C++ code
int *ptr1=(int *)(&a+1);  
首先,&a获得的是数组的地址,这样增长的单元则成了sizeof(a)=20
这样ptr1指向a[5](当然,数组的下标是从0开始,因此正常只有0-4有效.)
ptr1[-1]相当于ptrl-1,也就相当于a[4],因此输出为5.



第二点:  

C/C++ code
int *ptr2=(int *)((int )a+1);  
(int )a==>能过强制类型转换,此时的(int)a只为一个般的int型值.其值为a[0]的地址.
(int)a+1==>其值为a[0]的地址+1
int *ptr2=(int *)((int )a+1);==>此时指向的地址为00 00 00 02  
相当于数值02 00 00 00(十进制为:33554432)
最后由于
printf("%x",*ptr2);==>要求输出为16进制的格式,当然就是2000000



*/
/*
mark 4:

在《C和指针》165页第8章数组的总结中有这样一段话:  

在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。
这个规则只有两个例外。sizeof返回整个数组所占用的字节而不是一个指针所占用的字节。
单目操作符&返回一个指向数组的指针,而不是一个指向数组第1个元素的指针的指针。  

int *ptr1=(int *)(&a+1); //a是指向数组的指针  
int *ptr2=(int *)((int )a+1); //a是指向数组第1个元素的指针  

看来基础很重要啊!



ptr1[-1]在《C和指针》里也有解释,等价于*(ptr1-1)。
注意这里ptr是指向int而不是int数组的指针。  

*/

/* mark 5:

在c语言里  
由于e1[e2]的定义就是 *(e1+e2)  
所以(-2)[a]就是*(a-2)  

*/

/*
mark 6:

1)记着这样的原则:用最简单的表达,最清晰的设计,完成最复杂的设计,
  达到最干练的境界!否则一切都是扯淡!这是工程派的观点  
2)该题对于计算机体系结构及C语法的深入探究的确有帮助,但明显的是学术派的.  
3)用于对刚毕业的计算机的高求过高,而且有误导的嫌疑!  
4)LZ试回忆下你刚出道时真正理解volatile的用法吗?能把复杂的结构体根据
  内存对齐的三大原则(规范)计算的准确无误吗?显然不能!  
5)计算机及程序设计很容易,不是那么仙道样难学的,更不要以懂一些小技巧就瞒天过海,
  欺名盗世还自以为了不起!回想下自己走的路就知道,人真想掌握计算机是多么的容易,
  不是那么难的,只要不是傻瓜都可以成为计算机大牛的!!  
6)坦率的说,第一次见到本题我也呆了:居然还有这样的高手!
  本人5年C,4年底层驱动,天天和代码/计算机底层及硬件打交道,著名大牛的书都认真
  拜读过,但是却被这题震住了,但后来一想:用ptr[-1]来寻址的,饶来饶去把自己搞糊涂的
  只有LZ这样的顶尖高手!我不的不怀疑Brian .w .Kernighan < <程序设计实践> > 的真实性了!
  难道LZ比他老人家还厉害?????

*/

/*
mark 7:

其实我感觉也不是现在的学生差,而是感觉现在的学校的风气不好.  
既然楼上各位已经各抒己见了,那小弟也分析一下:  
  先说  
  int *ptr1=(int *)(&a+1);  
  其实(int *)只不过是说明赋个ptr1的是一个int型地址,按c默认的  
  也是整型,所以去掉(int *)应该不影响结果.那么就只剩下了(&a+1)  
  a--整型数组的首地址,没有问题.由于这个地址本身为int型,所以a+1  
  就是从a所指向的地址开始往下移动8个单元(注意:这里所说的单元是  
  计算机基本单元--字节,整型在C中占两个字节).此时ptr1就是a[4],  
  但是要记住!读取内存数据的时候是从高字节开始读的(学过汇编语言  
  的是不会陌生的).所以ptr1[0]不是5,而ptri[-1]才是5.  
  再说  
  int *ptr2=(int *)((int)a+1);  
  (int *)同上.(int)a+1相当于((int)a)+1在这里指针指向的是a的下  
  一个内存字节单元.那么在读取的时候还是从高地址开始读,在高地址读  
  出的是02,在低地址读出的是00,所以就出现了结果里的0200;  
  不知这样解释是否有道理,请各位批评指正!!   
*/

 

 

Vc6.0的,就是windows下的,那么一般在intel上内存是这样的 小端模式 01 00 00 00 02 00 00 00 …… ^ (int)a + 1,就是向前移了一个字节 01 00 00 00 02 00 00 00 …… ^ 于是结果是02 00 00 00(十六进制) 太简单了,学过汇编这种简直是小儿科!只能说现在的大学生都去吃java,C#的糖衣炮弹了!

 

楼主的题目本身有点问题;
但是不失为一个好题目;
解答过程如下:
首先,题目要求知道int的长度,我们可以认为默然是32bit系统也就是4;同时我们必须知道是小端还是大端法;
这个直接和答案有关
首先我们来看数组a;他在内存中可能2种方式;也就是和小端和大端有关,我从小地址开始写起
1:00,00,00,01, 00,00,00,02, 00,00,00,03, 00,00,00,04, 00,00,00,05,**
2:01,00,00,00, 02,00,00,00, 03,00,00,00, 04,00,00,00, 05,00,00,00,**  
int *ptr1=(int *)(&a+1);
int *ptr2=(int *)((int )a+1);
ptr1 指向了最后的**地址因为&a的类型是int (*)[5];但是这个时候ptr1的类型却是int *的,那么ptr1[-1]就指向了05,或则00
ptr2 指向了第2个00;所以*ptr2 = 00,00,01,00或则00,00,00,02;

 

 

http://topic.csdn.net/u/20071127/17/d521c586-bc0a-4c31-bcfe-5d90f0ed7c5e.html

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值