繁体   English   中英

指向指定为二维数组的指针的指针指向错误的地址

[英]Pointer to pointer assigned to a two-dimensional array, points to wrong address

我是C的新手,虽然我认为我几乎得到了关于指针和数组的整个逻辑,但我遇到了一个对我没有任何意义的问题。

比如说,考虑一个二维数组

double arr[][3] = { {1,2,3}, {4,5,6} };

指向指针的指针

double ** ptrptr;

现在,假设分别打印arrarr[0]指向的地址,即

printf( "%ld \n", (long) arr);
printf( "%ld \n", (long) *arr);

产生类似的东西

140734902565640
140734902565640

因为arr (数组数组)的第一个元素和*arr (双精度数组)的第一个元素具有相同的位置 到现在为止还挺好。 现在我的问题:

我这样做:

ptrptr = (double **) arr;
printf( "%ld \n", (long) ptrptr);
printf( "%ld \n", (long) *ptrptr);

和我期望的输出作为前一样, 而是我得到类似

140734902565640
4607182418800017408

所以似乎ptrptrarr都评估指向同一位置的指针 - 正如预期的那样 - 但是*ptrptr*arr 没有 为什么?
此外,如果我再次取消引用,即**ptrptr ,我会得到分段错误。

我的逻辑是...... 像这样,松散地说:
ptrptr == arr == &arr[0]应该指向
*ptrptr == *arr == arr[0] == &arr[0][0]反过来应指向
**ptrptr == *arr[0] == arr[0][0]

虽然我找到了一个符合我目的的解决方法,但我仍然很想知道为什么这不起作用。

我们来谈谈表达式类型

除非它是sizeof或一元&运算符的操作数,或者是用于初始化声明中的另一个数组的字符串文字,否则将“N元素数组T ”的表达式转换(“衰减”)为表达式“指向T指针”,表达式的值是数组的第一个元素的地址。

表达 arr具有类型“的3元素数组的2个元素的数组double ”。 在线

printf( "%ld \n", (long) arr);

arr不是&sizeof运算符的操作数,因此它被转换为“指向double元素的3元素数组的指针”类型的表达式,其值是第一个元素的地址,或者是&arr[0]

在线

printf( "%ld \n", (long) *arr);

因为表达式arr具有类型“指向double元素的3元素数组的指针”,所以表达式*arr (相当于表达式arr[0] )具有类型“ double -3-element array of double ”。 由于此表达式不是sizeof或一元&运算符的操作数,因此它将转换为“指向double指针”类型的表达式,并且其值是第一个元素的地址,或者是&arr[0][0]

在C中,数组的地址与数组的第一个元素的地址相同(没有为指向第一个元素的指针留出单独的存储空间;它是从数组表达式本身计算的)。 数组在内存中布局为


         +---+
    arr: | 1 |  0x0x7fffe59a6ae0
         +---+
         | 2 |  0x0x7fffe59a6ae8
         +---+
         | 3 |  0x0x7fffe59a6aec
         +---+
         | 4 |  0x0x7fffe59a6af0
         +---+
         | 5 |  0x0x7fffe59a6af8
         +---+
         | 6 |  0x0x7fffe59a6afc
         +---+

所以下面的表达式都会产生相同的 ,但类型会有所不同:

Expression            Type                Decays to     
----------            ----                ---------
       arr            double [2][3]       double (*)[3]
      &arr            double (*)[2][3]    n/a
      *arr            double [3]          double *
    arr[i]            double [3]          double *
   &arr[i]            double (*)[3]       n/a

*arr[i]arr[i][j]都产生double值。

现在让我们来看看ptrptr

double **ptrptr = (double **) arr;
printf( "%ld \n", (long) ptrptr);
printf( "%ld \n", (long) *ptrptr);

我们注意到的第一件事是ptrptr 不是数组表达式 ,因此上面的转换规则不适用。 我们将它指定为指向arr的第一个元素,但之后它的行为与任何其他指针一样,因此表达式ptrptr*ptrptr 具有不同的值。 由于ptrptr指向数组的第一个元素( &arr[0][0] ), *ptrptr产生存储在第一个元素的 ,即1.00 恰巧如此,当你在这个特定平台上将1.00的位模式解释为长整数时,它就是4607182418800017408。

这里有一些代码可以使上面的内容更加清晰:

#include <stdio.h>

int main( void )
{
  double arr[][3] = {{1,2,3},{4,5,6}};
  double **ptrptr = (double **) arr;

  printf( "    arr: %p\n", (void *) arr );
  printf( "   &arr: %p\n", (void *) &arr );
  printf( "   *arr: %p\n", (void *) *arr );
  printf( " arr[0]: %p\n", (void *) arr[0] );
  printf( "&arr[0]: %p\n", (void *) &arr[0] );

  printf( " ptrptr: %p\n", (void *) ptrptr );
  printf( "*ptrptr: %p (%f %ld)\n", (void *) *ptrptr, 
     *(double *) ptrptr, *(long int *) ptrptr );

  return 0;
}

这是输出:

    arr: 0x7fffe59a6ae0
   &arr: 0x7fffe59a6ae0
   *arr: 0x7fffe59a6ae0
 arr[0]: 0x7fffe59a6ae0
&arr[0]: 0x7fffe59a6ae0
 ptrptr: 0x7fffe59a6ae0
*ptrptr: 0x3ff0000000000000 (1.000000 4607182418800017408)

同样, *ptrptr给出了数组第一个元素的值,即1.0 ,但我们将该位模式解释为指针值( 0x3ff0000000000000 )和长整数( 4607182418800017408 )。

用图表清楚地说明了这种差异。

这是C中的普通二维数组, double[4][3]

+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+
|   |   |   |
+---+---+---+

这是C中的双指针, double ** 它指向double *[4]的头部,每个都指向double[3]的头部。

+----------+     +---+       +---+---+---+
|          +---->|   +------>|   |   |   |
+----------+     +---+       +---+---+---+
                 |   +-----+
                 +---+     | +---+---+---+
                 |   +---+ +>|   |   |   |
                 +---+   |   +---+---+---+
                 |   +-+ |
                 +---+ | |   +---+---+---+
                       | +-->|   |   |   |
                       |     +---+---+---+
                       |
                       |     +---+---+---+
                       +---->|   |   |   |
                             +---+---+---+

请注意,访问元素的语法是相同的。

double arr[4][3];
arr[1][2] = 6.28;

double **ptr = ...;
ptr[1][2] = 6.28;

然而,实际发生的情况却截然不同。 所以你不能简单地将一种类型转换为另一种类型。

使用double[4][3] ,您可以通过一些数学运算来计算偏移量。

使用double ** ,您可以通过跟随指针链找到一个元素。

数组可以看作是“标签”:

与指针不同,它没有lvalue ,并且在声明后不能将其设置为不同的值。

尝试arr = ptrptrarr = (double**)0x80000000 ,您将收到编译错误。

这就是为什么,对于任何类型的数组arr ,以下总是如此: arr == &arr

另外,请注意内存映射的差异:

double arr[3][3]; // A consecutive piece of 9 `double` units.

double** ptrptr                // 1 pointer unit.
ptrptr = new double*[3];       // A consecutive piece of 3 pointer units.
for (int i=0; i<3; i++)
    ptrptr[i] = new double[3]; // 3 separate pieces of 3 consecutive `double` units.

因此,与arr相比, ptrptr不仅消耗额外的4个指针单元,而且还在内存中的3个独立部分中分配了9个double单元。

因此arr = &arr = &arr[0] = &arr[0][0] ,但是ptrptr != *ptrptr != **ptrptr

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM