c语言 - "array[x][y]"总是用指针算术计算吗?



根据我在C:中的理解

当您声明像int array[x][y] = {0};这样的2D数组时,程序会得到一块int的长内存(x*y)。

当你对一个2D阵列进行malloc时,比如:

int ** array = malloc(sizeof(int*) * x);
for(int i=0;i<x;i++) {array[i] = malloc(sizeof(int) * y)};

该程序获得一块内存(x个int*)+(x*y)个int的长度。

我遇到的问题是:当您稍后键入array[5][0]时,会发生什么?程序是否总是将其视为指针算术(这意味着编译器在声明数组时会为您创建一个指针数组)?还是编译器根据您创建数组的方式对该语句进行不同的处理?

编辑:已更改";int*数组";至";int**数组";

C在其前身B上添加的一大创新是,没有存储数组的基指针的地方,这意味着名称不是为指向第一个元素的指针命名,而是为数组本身命名。

数组是基本类型的数组、用户定义类型(结构、联合)的数组还是数组类型的数组都不会改变任何内容。

因此,是的,数组衰减到一个指针,该指针用于指针算术(具有讽刺意味的是,数组索引是指针算术加上解引用的糖),产生一个数组类型的数组元素,并且在指针衰减之后,该指针又用于指针算术。

计算出的所有中间值都只是这些,不需要存储在其他地方。

第二个例子实际上不是一个多维数组,而是一个指针数组,虽然使用相同的语法进行访问,但却是一个不同的野兽。

C使用操作数的类型来决定如何对其求值。

如果arrayint [x][y],则在array[5][0]中:

  • array是一个数组,因此它会自动转换为指向其初始元素的指针。让我们调用这个指针的值p
  • p[5]是指索引为5的array的元素
  • p[5]也是一个数组,因此它被转换为指向其初始元素的指针。让我们调用这个指针的值q
  • 则CCD_ 13指代索引为0的CCD_
  • 因此,我们有array的元素5的元素0

如果arrayint **,则在array[5][0]中:

  • array是一个不是数组类型的左值,因此它会自动转换为存储在其中的值。请注意,当array是数组类型时,指向其第一个元素的指针是通过知道数组存储位置来计算的。这里,在array不具有数组类型的情况下,从内存中获取值。再次调用加载的值p
  • 那么p[5]指的是索引为5的元素,假定存在存储在p点处的元素数组
  • 因为array的类型是int **,所以它指向的事物具有int *类型。因此CCD_ 26具有类型CCD_ 27
  • p[5]是一个不是数组类型的左值,因此它会自动转换为存储在其中的值。同样,这是通过从内存加载存储的值来完成的。让我们调用加载的值q
  • q[0]是指q指向的元素。因此,我们有一个从p[5]指向的偏移0处的元素,而p[5]是从array指向的偏移5处的元素

因此array[5][0]的计算方法不同。当它是阵列的阵列时,存储器地址是根据array的基地址计算的。当它是int **时,通过从存储器加载指针来计算存储器地址。

备注

左值转换是如此的自动和普遍,以至于我们经常不去想它。在x = y + z;中,yz指的是对象,这些对象的值会自动加载并在表达式中使用。这被称为左值转换x也指对象,但它不会转换为其值,因为赋值运算符的左操作数存在异常。(当左值是sizeof、一元&++--的操作数或是.的左操作数时,也不会发生转换。)

在某些语言中,没有自动的左值转换,必须显式地加载值。例如,在BLISS中,必须写入x = .y + .z,其中.表示要加载值。

您的代码无效-应该是:

int **array = malloc(sizeof(int*) * x);
//or better
int **array = malloc(sizeof(*array) * x);

但通过这种方式,您不会仅为2D数组分配一个指针数组。

在这种情况下,程序必须首先取消引用指针数组。然后使用这个指针,第二个索引将引用int值。它的效率不是很高,因为至少需要从内存中读取两次。

2D阵列被分配为一个内存块。元素在内存中的位置由程序计算,无需从内存中进行额外读取。https://godbolt.org/z/5adjqxeKP

要动态分配2d数组,您需要使用指向数组的指针:

int (*array)[x] = malloc(sizeof(*array) * y);
int (*array1)[x][Y] = malloc(sizeof(*array));

和参考:

array[3][2] = 5;
(*array1)[4][5] = 6;

数组索引相当于指针算术加上解引用。具体而言,E1[E2]*((E1) + (E2))完全相同

在2D数组或指针到指针的情况下,这种情况会发生两次。以array[5][0]为例,这与*(array[5] + 0)相同,后者与*(*(array + 5) + 0)相同。

至于指针算术方面会发生什么,首先让我们看看2D数组的情况。在表达式array + 5中,array被转换为指向其第一个元素的指针,因此具有类型int(*)[y]。因此,将5加到该指针上会将得到的指针向上移动它所指向的大小(即int[y])乘以5。

对于指针到指针,array + 5将结果点向上移动指向的大小(即int *)乘以5。

所以它是完全相同的表达式,但指针算法不同,因为所指向的是不同的。

图片可能会有所帮助。为了节省空间,我们假设xy是2。给定声明
int arr[2][2];

我们在记忆中得到了这个:

int
+–––+
arr: |   | arr[0][0]
+–––+
|   | arr[0][1]
+–––+
|   | arr[1][0]
+–—-+ 
|   | arr[1][1]
+–––+

请注意,没有为任何指针留出空间——没有与数组元素本身分离的对象arr

对于代码

int **arr = malloc( 2 * sizeof *arr );
for ( size_t i = 0; i < 2; i++ )
arr[i] = malloc( 2 * sizeof *arr[i] );

我们得到这个:

int **    int *              int
+–––+     +–––+              +–––+
arr: |   | -–> |   | arr[0] ––––> |   | arr[0][0]
+–––+     +–––+              +–––+
|   | arr[1] ––+   |   | arr[0][1]
+–––+          |   +–––+
|
|   +–––+
+–> |   | arr[1][0]
+–––+
|   | arr[1][1]
+–––+

在这种情况下,您有三个指针-arr指向指针序列,每个指针指向int序列。

那么,如何对arr[x][y]进行评估呢?

请记住,表达式a[i]定义为*(a + i)-给定地址a,从该地址偏移i元素(而不是字节!),并取消引用结果。

arr[i][j] == *(arr[i] + j) == *(*(arr + i) + j)

如果CCD_ 68是CCD_ 69的2D阵列或指向CCD_。

在第二种情况下,情况非常明显——我们处理的是一堆显式指针。arr显式地存储arr[0]的地址,arr[0]显式地保存arr[0][0]的地址,等等。所以arr[i] == *(arr + i)arr[i][j] == *(*(arr + i) + j)是完全合乎逻辑的。

但是第一个病例呢?任何地方都不会显式存储指针。arr不存储arr[0]的地址(没有单独的arr[0],这意味着没有什么可以存储arr[0][0]的地址)。那么,如何将arr[i][j]评价为*(*(arr + i) + j)呢?

就像这样-除非它是sizeof或一元&运算符的操作数,或者是用于初始化char数组的字符串文字,否则是类型为"的表达式;CCD_ 86〃的N元阵列;将被转换;衰变";,转换为"类型的表达式;指向CCD_ 87的指针";并且表达式的值将是数组的第一个元素的地址。

当编译器在代码中看到表达式arr时,除非该表达式是sizeof或一元&的操作数,否则会用指针替换该表达式,并且该指针的值是数组第一个元素的地址。类似地,表达式arr[i]也被替换为指针,并且该指针值是arr[i][0]的地址。注意,在这种情况下CCD_;指向int2-元素数组的指针";(int (*)[2])而不是";指针到指向int的指针";。

Expression    Type          Decays to    Value
----------    ----          ---------    -----
arr    int [2][2]    int (*)[2]   Same as &arr[0]
*arr    int [2]       int *        Same as arr[0]
arr[i]    int [2]       int *        Same as &arr[i][0]
*arr[i]    int           n/a          Same as arr[0][0]
arr[i][j]    int           n/a
&arr    int (*)[2][2] n/a          Address of array object
&arr[i]    int (*)[2]    n/a          Address of the i'th subarray

所以我们可以把arr[i][j]看作是这样评估的:

*(*(&arr[0] + i) + j)

数组的地址与其第一个元素的地址相同——表达式&arrarr&arr[0]arr[0]&arr[0][0]都产生相同的地址值,但表达式的类型不同——int (*)[2][2]int (*)[2]int (*)[2]int [2] => int *,和int *(这可能会影响指针值的表示方式——int (*)[2]可能与int *有不同的表示方式,尽管在您可能遇到的任何系统上都不是这样)。

记住指针算术是如何工作的——如果p指向T类型的对象,那么p + 1将产生该类型的下一个对象的地址。如果arr指向int的2元素数组,则arr + 1产生int的下一个2元素数组的地址。回到我们的第一张照片,但现在有一些额外的表达:

int                 int (*)[2][2]  int (*)[2]  int *
+–––+               -------------  ----------  -----
|   | arr[0][0] <-- &arr           arr         *arr + 0       (arr[0] + 0)
+–––+      
|   | arr[0][1] <--                            *arr + 1       (arr[0] + 1)
+–––+
|   | arr[1][0] <--                arr + 1     *(arr + 1) + 0 (arr[1] + 0)
+–—-+ 
|   | arr[1][1] <--                            *(arr + 1) + 1 (arr[1] + 1)
+–––+

同样,每当编译器在表达式中看到arr时,它都会将其替换为&arr[0]的值,并使用指针算术进行下标。

好吧,

int variable[5][10];

将变量声明为5个元素的单个数组,这些元素都属于int[10]类型(10个元素的数组),其单元格大小(假设int为4个字节)为10*4=40个字节。这意味着variable[3]是第四个元素,位于变量加3x40=120字节的地址。

这里有一个由10个ints组成的数组,如果你试图访问第三个元素(variable[3][2]),你必须添加2个sizeof(int),这是8个字节。

所以,是的,对于2D数组,指针运算和1D数组一样有效。但是,当您使用malloc动态分配一些东西时,您需要知道编译器管理数组类型(类型是sometype[n][m],不同的nm是不同的类型,*C中的所有类型在编译时都必须已知——这是C静态类型特征的一部分——)

如果您想使用某种方式在C中指定动态数组(使用符号[a][b]...[z]访问的数组),其中空间是动态分配的,并且只分配所需的空间。。。您必须使用指针来解决所有中间访问,因为运行时没有关于数组大小的信息,因为您使用的是指针,而不是数组类型。不能声明数组类型并使其不完整。。。你用了一个指针。即使在参数声明中,当您声明数组类型的参数时,它也会自动转换为指针。。。因为不能按值传递数组,而且c语言不检查数组边界。

所以指针运算本质上是一维的,你可以为动态数组使用指针。。。编译器不知道单元格(或行)的大小。。。所以它不能假设每个行单元是26个整数大小或更小。。。您需要将一个指针放在数据的开头(解决上一次到最后一次的计算),并且必须创建axbxc。。。。xy y指针(双、三、四等),直到分配完整个数组。

如何做到这一点(例如,通过m处理n的2D矩阵)?

double **m = malloc(n * sizeof(*m));  /* size of n pointers to double */
assert(m != NULL);
for (int row = 0; row < n; row++) {
m[row] = malloc(m * sizeof m[row][0]); 
assert(m[row] != NULL);
}

这将分配伪矩阵,其中q[0]0将是有效的double元素并且可以被访问,但是m[2]不是m元素的阵列,而是指向m元素的阵列的指针。

相关内容

  • 没有找到相关文章

最新更新