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

时间:2014-02-04 22:37:47

标签: c pointers multidimensional-array

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

考虑 2D数组,比如说

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]

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

3 个答案:

答案 0 :(得分:3)

我们来谈谈表达式类型

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

表达式 arr具有类型为“{element 1}}的3元素数组的2元素数组”。在行

double

printf( "%ld \n", (long) arr); 不是arr&运算符的操作数,因此它被转换为类型为“指向sizeof的3元素数组的指针”的表达式,它的值是第一个元素的地址,或double

在第

&arr[0]

因为表达式printf( "%ld \n", (long) *arr); 具有类型“指向arr的3元素数组的指针”,所以表达式double(相当于表达式*arr)具有类型“arr[0]的3元素数组”。由于此表达式不是double或一元sizeof运算符的操作数,因此它将转换为“指向&的指针”类型的表达式,其值为第一个元素,或double

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

&arr[0][0]

因此,以下表达式将产生相同的,但类型将不同:


         +---+
    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的第一个元素,但之后它的行为与任何其他指针一样,因此表达式arrptrptr 会有所不同值。由于*ptrptr指向数组的第一个元素(ptrptr),&arr[0][0]会生成存储在第一个元素的,即*ptrptr 。恰巧如此,当您将1.00的位模式解释为此特定平台上的长整数时,它将显示为4607182418800017408。

以下是一些可能使上述内容更清晰的代码:

1.00

这是输出:

#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)。

答案 1 :(得分:2)

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

这是C中的普通2D数组,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 **,您可以通过一系列指针找到一个元素。

答案 2 :(得分:1)

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

与指针不同,它没有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.

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

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