C数组和指针相关的问题

blog 搬迁中 新地址 http://www.naimin.net

两个面试题

面试时发现很多公司喜欢考一些数组和指针相关的问题。比较常见的是下面这个:

/* main.c */

int a[4] = {1, 2, 3, 4};
extern void foo();
int main()
{
    foo();
    return 0;   
}

/* foo.c */
#include<stdio.h>
extern int *a;
void foo()
{
    printf("%d \n", a[0]);
}

问题是这段代码是否可以正常运行?

最近还碰到个题目是这样的:

int a[5] = {1, 2, 3, 4, 5};
int *p = &a + 1;
printf("%d", *p);

这段代码的输出是什么?

在表达式中数组名字和指针的关系

要解决上面的两个问题,就要搞清数组名字和指针的关系。
在C中,大部分的表达式中数组名字的值就是数组第一个元素的地址,指针类型是和元素的类型一样,但是一个const的指针。
对于下面这种定义:

int a[10];
int *const p;

a是一个整型的的const指针, 和p的类型是一样的。

作为函数参数时:

void func2( int *p );
void func2( int a[] );

这两种声明也是等价的。 但绝不能认为数组名字和指针就是等价的。在表达式中有2个例外:

  • sizeof: sizeof(a) 的值是数组的大小 ,不是第一个元素指针的大小。
  • &操作符。 &操作符返回的是第一个元素的地址,所以下面代码中三个printf输出的地址是一样的:
int a[5];
printf("%x\n", a);
printf("%x\n", &a[0]);
printf("%x\n", &a);

但有意思的是上面代码中&a的类型是 int (*)[5],所以下面代码的输出是这样的:

int a[5];
printf("%x\n", &a);
printf("%x\n", &a + 1);
----------------------------------
output: 2686740 
        2686760

可以看到地址差了20个byte, 就是int a[5]所占用的空间。
所以上面第二个题目的答案是不确定,因为p这个指针已经越界了,不在数组a的范围内了。

编译器对于数组和指针的处理是不同的

对于第一个问题

/* main.c */

int a[4] = {1, 2, 3, 4};
extern void foo();
int main()
{
    foo();
    return 0;   
}

/* foo.c */
extern int a[];
void foo()
{
    int x;
    x = a[0];
}

我们来看下这段代码的汇编的代码:

gcc -o ptr_test main.c foo.c
objdump -d ptr_test 
080483c8 <foo>:
 80483c8:   55                      push   %ebp
 80483c9:   89 e5                   mov    %esp,%ebp
 80483cb:   83 ec 10                sub    $0x10,%esp
 80483ce:   a1 18 a0 04 08          mov    0x804a018,%eax
 80483d3:   89 45 fc                mov    %eax,-0x4(%ebp)
 80483d6:   c9                      leave  
 80483d7:   c3                      ret 

我们修改下foo.c

/* foo.c */
extern int *a;
void foo()
{
    int x;
    x = a[0];
}

然后看下汇编代码:

 080483c8 <foo>:
 80483c8:   55                      push   %ebp
 80483c9:   89 e5                   mov    %esp,%ebp
 80483cb:   83 ec 10                sub    $0x10,%esp
 80483ce:   a1 18 a0 04 08          mov    0x804a018,%eax
 80483d3:   8b 00                   mov    (%eax),%eax
 80483d5:   89 45 fc                mov    %eax,-0x4(%ebp)
 80483d8:   c9                      leave  
 80483d9:   c3                      ret  

可以发现不同的地方就是第二段代码多了 mov (%eax),%eax

为什么会这样呢?
1. 首先,在C语言中 数组下标 array[subscript] 等价于 *( array + ( subscript ) )。 也就是说编译器会将代码中a[0]转化为*(a + 0)也即是 *a, 所以代码中 x = a[0] 会变成 x = *a 。
2. 上面汇编代码中 0x804a018 是全局数组a的地址。 mov 0x804a018,%eax 这句代码就是把地址0x804a018的数据放到寄存器eax中,在我们的代码中地址0x804a018的整型数据就是1。所以eax中的数值就是1。
- 如果声明a为数组,*a的值就是eax中的值。 %eax,-0x4(%ebp)这就就是把*a的值赋给x。
- 如果声明a为指针,程序为了获得*a的值,就要去地址a取值。mov (%eax),%eax,这就是到eax中的值的地址去取整型数据,然后copy到eax中。 这就是一个取值的动作。因为eax中数据是1, 所以对这个地址取值是非法的,程序会终止。

所以第一个问题就是因为编译器对数组和指针的处理是不同的。

  • 对于数组a, 只会到a的地址取值,然后这个值就是*a的值,也就是1。
    0x804a018
    | 1 | 2 | 3 | 4 |

  • 对于指针a, 首先要到a的地址取值,这个值是指针a的值,也就是1。对于*a, 还要有个取值的动作,就是到地址为指针a的值的地方取值,所以就是到地址1去取值。
    0x804a018
    | 1 | 2 | 3 | 4 |
    1
    | ? | ? | ? | ? |

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值