数组与指针

1.定义

  指针:在K&R C上的原文为“A pointer is a group of cell (often two or four) that can hold an address.” 从概念上看,指针是包含地址的变量。
  数组:能存储连续固定数量的相同类型元素的数据结构。

2.指针数组和数组指针

int *arr_for_p[5];      // 指针数组
int (*p_for_arr)[5];    // 数组指针

由于[]的优先级高于*,所以第一条语句可以写成这样

int* (arr_for_p[5]);

而第二条语句的解释可以理解为(*p_for_arr)代表了一个5个整数的数组,而p_for_arr就是这个数组的指针,也就是这个数组首地址,所以使用起来类似于(*p_for_arr)[i]。实际上p_for_arr和*p_for_arr是同一个值,因为指针变量指向的是数组的首地址,而指针变量取消引用之后得到的是这个数组(实质上也是数组的首地址),所以它们的值是一样的。

3.数组首地址与数组首元素的首地址

#include <stdio.h>

int main(void) {
    int a[5];

    int (*ptr_1)[5] = a;
    printf("ptr_1 = %p(%u)\n", ptr_1, ptr_1);
    int (*ptr_2)[5] = &a;
    printf("ptr_2 = %p(%u)\n", ptr_2, ptr_2);

    return 0;
}

/*      
        结果:
        ptr_1 = 0xbfc9a144(3217662276)
        ptr_2 = 0xbfc9a144(3217662276)
 */

从C的层面上理解a是数组首元素的首地址,而&a是数组的地址,很显然在栈上分配的数组是一段连续的内存,这里是20个字节,那么可以知道a和&a的值就是一样的——都是这段内存的起始地址,去掉打印语句我们得到的汇编代码如下

main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $32, %esp
    leal    -28(%ebp), %eax    # a
    movl    %eax, -8(%ebp)
    leal    -28(%ebp), %eax    # &a
    movl    %eax, -4(%ebp)
    movl    $0, %eax
    leave
    ret

在汇编层面上它们就是相同的值,都是%ebp-28。

4.数组指针运算

#include <stdio.h>

int main(void) {
    int a[5];
    int (*ptr_a)[3] = a;
    int (*ptr_b)[5] = a;
    int (*ptr_c)[10] = a;

    unsigned long tmp = a;
    printf("tmp = %p(%d)\n", tmp, tmp);
    tmp = ptr_a+1;
    printf("tmp = %p(%d)\n", tmp, tmp);
    tmp = ptr_b+1;
    printf("tmp = %p(%d)\n", tmp, tmp);
    tmp = ptr_c+1;
    printf("tmp = %p(%d)\n", tmp, tmp);

    return 0;
}

/*
    结果:
    tmp = 0020FAA8(2161320)
    tmp = 0020FAB4(2161332)
    tmp = 0020FABC(2161340) 
    tmp = 0020FAD0(2161360)
 */

可以看到三个数组的指针加1后的偏移都不同,分别是12,20和40,所以它们是以数组为单位偏移,而这些都是编译器的工作。

5.多维数组

本质上多维数组和一维数组并无二致,只是进行多次下标运算而已,看下面的代码

int arrs[10][10];

printf("arrs = %p\n", arrs);
printf("arrs[0] = %p\n", arrs[0]);
printf("&arrs[0][0] = %p\n", &arrs[0][0]);

/*
 * 在Visual Studio 2010 中显示结果为:
 * arrs = 0031FBB4
 * arrs[0] = 0031FBB4
 * &arrs[0][0] = 0031FBB4
 */

6.多级指针

多级指针本质上也是指针,也就是含有地址的变量,关键是这个地址要合法。

7.多级指针与多维数组

我个人认为这两者没有什么必然的联系,除非动态分配多维数组,不过始终记住多维数组下标运算编译器采用的是首地址偏移的方式,而多级指针下标运算采用的是指针多次解除引用的方式,二者完全不同。请看下面的代码

int main(void) {
        int arrs[3][4];
        int** pparrs = (int **)arrs;
        
        arrs[2][2] = 33;
        pparrs[2][2] = 33;
        
        return 0;
}

汇编代码如下

main:
    pushl   %ebp
    movl    %esp, %ebp
    subl    $64, %esp
    leal    -52(%ebp), %eax  # 下面是直接偏移寻址
    movl    %eax, -4(%ebp)
    movl    $33, -12(%ebp)
    movl    -4(%ebp), %eax  # 下面是取消多级引用
    addl    $8, %eax
    movl    (%eax), %eax
    addl    $8, %eax
    movl    $33, (%eax)
    movl    $0, %eax
    leave
    ret

当然上面的代码直接运行肯定会宕掉。

posted @ 2013-08-08 15:20  WendellYih  阅读(242)  评论(0编辑  收藏  举报