【问题标题】:Pointer to pointer assigned to a two-dimensional array, points to wrong address指向分配给二维数组的指针的指针,指向错误的地址
【发布时间】:2014-02-04 22:37:47
【问题描述】:

我是 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]

虽然我找到了一种可以达到我的目的的解决方法,但我仍然很想了解为什么这不起作用。

【问题讨论】:

  • 一个数组的地址,和数组的值是一样的(传给printf的时候数组衰减成指针)。指向数组的指针和指向该指针的指针不相同。
  • 请注意,双指针 != 二维数组。多个重复问题herehere 例如

标签: c pointers multidimensional-array


【解决方案1】:

我们来谈谈表达式类型

除非它是 sizeof 或一元 & 运算符的操作数,或者是用于在声明中初始化另一个数组的字符串字面量,否则为“T 的 N 元素数组”类型的表达式被转换(“decays”)为“pointer to T”类型的表达式,表达式的值是数组第一个元素的地址。

表达式 arr 的类型为“double 的 3 元素数组的 2 元素数组”。在行中

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

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

排队

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

由于表达式arr 的类型为“指向double 的三元素数组的指针”,因此表达式*arr(相当于表达式arr[0])的类型为“@987654338 的三元素数组@"。由于该表达式不是sizeof 或一元& 运算符的操作数,因此将其转换为“指向double”的类型的表达式,其值是第一个元素的地址,即@987654342 @。

在 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) 和长整数 (@987654364 @)。

【讨论】:

    【解决方案2】:

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

    这是 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 **,您可以通过指针链找到一个元素。

    【讨论】:

      【解决方案3】:

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

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

      尝试arr = ptrptrarr = (double**)0x80000000,你会得到一个编译错误。

      这就是为什么对于任何类型的数组arr,以下内容总是正确的:arr == &amp;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.
      

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

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

      【讨论】:

        猜你喜欢
        • 2016-04-19
        • 1970-01-01
        • 2023-03-24
        • 1970-01-01
        • 2017-06-11
        • 2012-01-26
        相关资源
        最近更新 更多