• 数组与指针


    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)
    ", ptr_1, ptr_1);
        int (*ptr_2)[5] = &a;
        printf("ptr_2 = %p(%u)
    ", 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)
    ", tmp, tmp);
        tmp = ptr_a+1;
        printf("tmp = %p(%d)
    ", tmp, tmp);
        tmp = ptr_b+1;
        printf("tmp = %p(%d)
    ", tmp, tmp);
        tmp = ptr_c+1;
        printf("tmp = %p(%d)
    ", 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
    ", arrs);
    printf("arrs[0] = %p
    ", arrs[0]);
    printf("&arrs[0][0] = %p
    ", &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

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

  • 相关阅读:
    ORA12560: TNS: 协议适配器错误的问题
    ibatis代码生成工具abator使用全过程
    DbHelper数据操作类
    眼睛有干涩、血丝、怕光,流泪,甚至红肿的现象吗
    Dot.Net代码生成器
    两分钟让你明白什么是ERP
    spring的b/s项目中配置log4j
    十面埋妇
    程序员发展的目标
    标准体重计算查询
  • 原文地址:https://www.cnblogs.com/wendellyi/p/3245884.html
Copyright © 2020-2023  润新知