代码之家  ›  专栏  ›  技术社区  ›  jeremy radcliff

为什么指针Artimetic可以处理非连续的2d数组?

  •  1
  • jeremy radcliff  · 技术社区  · 7 年前

    我的理解是,如果在本地声明2d数组,那么: int 2darr[x][y] ,它不是一个指针数组,其中每个指针指向自己的1d数组,而是一个1d数组,处理器在该数组上执行以下类型的指针运算 *(2darr + (row x nCols) + col) .

    在这种情况下,语法糖后面的指针算法 2darr[row][col] 这很有意义,因为我们的2d数组实际上只是一个大小相同的连续内存块 nRows x nCols .

    然而,动态分配2d数组的一种方法是首先分配一个大小为的指针数组 nRows ,然后为每个指针分配一个大小为 nCols 不管我们想要什么类型的。在这种情况下,我们的行不一定连续存储在内存中;每一行都可以存储在内存中完全不同的位置,指针数组中的一个指针指向它的第一个元素。

    有鉴于此,我不明白我们如何通过执行以下操作仍然可以访问2d阵列中的数据 2阵列[行][列] . 由于我们的行不能保证连续存储,因此 *(2darr+(行x nCols)+列) 根本不应该保证工作。

    2 回复  |  直到 7 年前
        1
  •  2
  •   Eric Postpischil    7 年前

    使用 SomeType A[M][N] 使用指向指针数组的指针实现的数组可以通过以下方式访问 A[i][j] 是由于下标运算符的工作方式、指针算法的工作方式以及数组到指针的自动转换。

    关键区别在于 A[我][j] 使用指针, A[i] 是一个指针,其值从内存中提取,然后与一起使用 [j] . 相反,在 A[我][j] 对于阵列, A[我] 是一个数组,其指针值基于数组本身;表达式中数组的使用将转换为指向其第一个元素的指针。二者都 A[我] 对于指针和 A[我] for数组需要在下一步中使用指针,但第一步是从内存中的指针加载的,第二步是从数组存储在内存中的位置计算的。

    首先,考虑定义为以下内容的数组:

    SomeType A[M][N];
    

    鉴于此,当表达式 A[我][j] 进行评估时,评估将继续:

    • A是一个数组。
    • 在这种情况下 1. ,数组将自动转换为指向其第一个元素的指针。我们把这个叫做 p . A 是的数组 M 元素,每个元素都是 N 要素 SomeType . 所以 p 是指向 N 要素 基类型 .
    • p 替换 A. ,因此表达式为 p[i][j] .
    • 下标的定义是 E1[E2] 与相同 (*(E1+E2)) . (为了简洁起见,我省略了形式定义中的括号。)当我们将其应用于第一个下标时, p【i】【j】 成为 (*(p+i)[j] .
    • 下一个 p+i 已评估。指针算法以指向类型为单位工作。自从 p 指向的数组 N 元素, p+i 从第一个数组(索引为0)移动到索引为0的数组 i . 让我们称之为 q .
    • 现在我们有了 (*q)[j] 哪里 q 指向元素 属于 A. . 请注意,此元素 q 指向的是 N 要素 基类型 .
    • 自从 q 指向数组, *q 阵列。
    • 此数组将自动转换为指向其第一个元素的指针。让我们称之为 r . r 指向数组的第一个元素 q 指向。
    • 现在我们有了 (r)[j] ,或删除括号, r[j] 哪里 r 指向作为元素的数组的元素0 属于 A. .
    • 同样,下标的定义表示这与 (*(r+j)) .
    • 按指针算法 r+j 指向元素 j 阵列的。
    • 自从 r+j 指向元素 j , *(r+j) 要素 j 阵列的。
    • 因此 A[我][j] is元素 j 索引的数组的 在里面 A. .

    现在考虑使用指向指针的指针实现的二维数组,如下代码所示:

    SomeType **A = malloc(M * sizeof *A);
    for (size_t i = 0; i < M; ++j)
        A[i] = malloc(N * sizeof *A[i]);
    

    (我们假设 malloc 呼叫成功。在生产代码中,应该对其进行测试。)

    鉴于此,当表达式 A[我][j] 进行评估时,评估将继续:

    • A是指向的指针 基类型 .
    • 根据下标的定义, A[我][j] 与相同 (*(A+i))[j] .
    • 通过指针运算, A+i 从何处移动 A. 指向 它之外的元素。在这种情况下, A. 指向指针(特别是指向SomeType的指针),因此指针算术的元素就是这些指针。所以 A+i 指向 超出第一个指针的指针。我们把这个叫做 q .
    • 现在我们有了 (*q)[j] 哪里 q 指向元素 在我们创建的指针数组中。
    • 自从 q 指向指针, *q 就是那个指针。我们把这个叫做 r . r 指向(的)第一个元素 基类型 )被分配到其中一个 马洛克 电话。
    • 现在我们有了 (r) 【j】 ,或删除括号, r【j】 哪里 r 指向元素 在指针数组中。
    • 同样,下标的定义表示这与 (*(r+j)) .
    • 按指针算法 r+j 指向元素 j 第一个元素所在的数组的 r 正在指向。
    • 自从 r+j 指向元素 j , *(r+j) 要素 j 阵列的。
    • 因此 A[我][j] is元素 j 索引的数组的 在里面 A. .

    脚注

    1. 类型为的数组的表达式 类型 转换为指向数组第一个元素的指针,除非它是 sizeof , _Alignof ,或一元 & 或是用于初始化数组的字符串文字。

        2
  •  2
  •   Some programmer dude    7 年前

    您的阵列 2darr 是的数组 阵列 .

    例如,定义如下

    int aa[2][3];
    

    是一个由两个元素组成的数组,每个元素依次是一个由三个元素组成的数组 int 价值观

    在记忆中看起来像这样

    +----------+----------+----------+----------+----------+----------+
    | aa[0][0] | aa[0][1] | aa[0][2] | aa[1][0] | aa[1][1] | aa[1][2] |
    +----------+----------+----------+----------+----------+----------+
    

    关于指针算法,可能会让您感到困惑的部分是针对任何数组(或指针!) a 和索引 i 表达式 a[i] 等于 *(a + i) .

    使用上面没有数组的“公式”,可以得到 aa[i] 是另一个数组。即。 *(aa + i) 是另一个数组,您可以对其使用索引,如 (*(aa + i))[j] . 当然,第二级索引也可以使用指针算法编写,如 *(*(aa + i) + j) .

    显示的表达式得到了什么,没有数组 aa 可能是 *(aa + i * 3 + j) ,对于数组数组而言是不正确的。我的意思是不会的 语义上 对的那是因为 *(aa+i*3+j) 真的和 aa[i * 3 + j] 在这种情况下 aa公司 数组 . 表达式 aa【i*3+j】 (因此 *(aa+i*3+j) )属于类型 int[3] . 这不是一个 内景 要素

    你的表情,在表格上 *(a + row * ncol + col) 仅当您有单个数组时才正确。喜欢

    int bb[6];  // 6 = 2 * 3
    

    现在 可以使用为数组编制索引 *(bb + i * 3 + j) (或 bb[i * 3 + j] ),结果将是 内景 价值


    使用指向指针的指针实现的“二维”数组(实际上不是)也称为 jagged array ,并且它不必是连续的。这意味着 *(2darr + (row x nCols) + col) 表达式确实无效。

    再举一个简单的例子:

    int **pp;
    
    pp = malloc(sizeof *pp * 2);  // Two elements in the "outer" array
    for (size_t i = 0; i < 2; ++i)
    {
        pp[i] = malloc(sizeof **pp * 3);  // Three elements in the "inner" array
    }
    

    上面的代码创建了一个类似的“二维”数组 aa公司 在上面最大的区别是它的内存布局

    +-------+-------+
    | pp[0] | pp[1] |
    +-------+-------+
     |       |
     |       v
     |       +----------+----------+----------+
     |       | pp[1][0] | pp[1][1] | pp[1][2] |
     |       +----------+----------+----------+
     v
     +----------+----------+----------+
     | pp[0][0] | pp[0][1] | pp[0][2] |
     +----------+----------+----------+
    

    对于外部阵列, pp[i] 仍等于 *(pp + i) ,但当 aa[一] 结果为三个数组 内景 元素, pp[一] 是指向的指针 内景 (即。 int * ).

    由于可以对指针使用数组索引语法,因此 pp[一] 可以编制索引,然后就可以使用“二维”语法 pp[i][j] .

    *(pp + i * 3 + j) 表达式无效,因为内存不连续,所以上面显示的所有其他指针算法都是。例如(如图所示) pp[一] 等于 *(pp+i) . 但由于这是一个可以索引的指针, (*(pp + i))[j] 也是有效的,也是有效的 *(*(pp + i) + j) .