我是C的新手,虽然我认为我几乎得到了关于指针和数组的整个逻辑,但我遇到了一个对我没有任何意义的问题。
考虑 2D数组,比如说
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]
。
虽然我找到了一个符合我目的的解决方法,但我仍然很想知道为什么这不起作用。
答案 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
的第一个元素,但之后它的行为与任何其他指针一样,因此表达式arr
和ptrptr
会有所不同值。由于*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 = 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.
因此,与ptrptr
相比,arr
不仅消耗额外数量的4个指针单位,而且还在内存中的3个独立部分中分配了9个double
单位。 / p>
因此arr = &arr = &arr[0] = &arr[0][0]
,但ptrptr != *ptrptr != **ptrptr
。