[英]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;
现在,假设分别打印arr
和arr[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
所以似乎ptrptr
和arr
都评估指向同一位置的指针 - 正如预期的那样 - 但是*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 = ptrptr
或arr = (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.