为什么 x [0] ! = x [0][0] ! = x [0][0][0] ?

我正在学习一些 C + + ,我正在与指针作斗争。我知道我可以通过声明3个指针级别:

int *(*x)[5];

因此,*x是一个指向包含5个元素的数组的指针,这5个元素是指向 int的指针。 我还知道 x[0] = *(x+0);x[1] = *(x+1)等等... ..。

那么,根据上述声明,为什么是 x[0] != x[0][0] != x[0][0][0]

15118 次浏览
x[0] != x[0][0] != x[0][0][0]

根据你自己的帖子,

*(x+0) != *(*(x+0)+0) != *(*(*(x+0)+0)+0)`

这是简化了的

*x != **x != ***x

为什么应该是相等的?
第一个是某个指针的地址。
第二个是另一个指针的地址。
第三个是 int值。

您试图通过值来比较不同的类型

如果你采取的地址,你可能会得到更多的你所期望的

记住,你的声明会产生影响

 int y [5][5][5];

将允许您想要的比较,因为 yy[0]y[0][0]y[0][0][0]将有不同的值和类型,但相同的地址

int **x[5];

不占据相邻的空间。

xx [0]的地址相同,但是 x[0][0]x[0][0][0]的地址不同

  • x[0]取消引用最外面的指针(指针指向指向 int 的指针大小为5的数组) ,并导致指向 int的指针大小为5的数组;
  • x[0][0]取消引用最外层指针 还有对数组进行索引,生成一个指向 int的指针;
  • x[0][0][0]取消对所有内容的引用,从而产生一个具体的值。

顺便说一下,如果您对这些声明的含义感到困惑,可以使用 Cdecl

让我们逐步考虑表达式 x[0]x[0][0]x[0][0][0]

按照以下方式定义 x

int *(*x)[5];

那么表达式 x[0]就是 int *[5]类型的数组。考虑到表达式 x[0]等效于表达式 *x。那就是解引用一个指向数组的指针我们得到了数组本身。让我们把它表示为 y,就是我们有一个声明

int * y[5];

表达式 x[0][0]等效于 y[0],具有类型 int *。让我们把它表示为 z,就是我们有一个声明

int *z;

表达 x[0][0][0]等效于表达 y[0][0],而表达 y[0][0]又等效于表达 z[0]并且具有类型 int

确实如此

x[0]int *[5]

x[0][0]int *

x[0][0][0]int

所以它们是不同类型,不同大小的物体。

举个例子

std::cout << sizeof( x[0] ) << std::endl;
std::cout << sizeof( x[0][0] ) << std::endl;
std::cout << sizeof( x[0][0][0] ) << std::endl;

x是一个指向包含5个指向 int的指针的数组的指针。
x[0]是一个由5个指向 int的指针组成的数组
x[0][0]是指向 int的指针。
x[0][0][0]int

                       x[0]
Pointer to array  +------+                                 x[0][0][0]
x -----------------> |      |         Pointer to int           +-------+
0x500 | 0x100| x[0][0]---------------->   0x100 |  10   |
x is a pointer to    |      |                                  +-------+
an array of 5        +------+
pointers to int      |      |         Pointer to int
0x504 | 0x222| x[0][1]---------------->   0x222
|      |
+------+
|      |         Pointer to int
0x508 | 0x001| x[0][2]---------------->   0x001
|      |
+------+
|      |         Pointer to int
0x50C | 0x123| x[0][3]---------------->   0x123
|      |
+------+
|      |         Pointer to int
0x510 | 0x000| x[0][4]---------------->   0x000
|      |
+------+

看得出来

  • x[0]是一个数组,当在表达式中使用时(除了一些例外情况) ,它将转换为指向其第一个元素的指针。因此,x[0]将给出它的第一个元素 x[0][0]的地址,即 0x500
  • x[0][0]包含一个 int的地址,即 0x100
  • x[0][0][0]包含 int10

所以,x[0]等于 &x[0][0]因此,等于 &x[0][0] != x[0][0]
因此,x[0] != x[0][0] != x[0][0][0]

下面是指针的内存布局:

   +------------------+
x: | address of array |
+------------------+
|
V
+-----------+-----------+-----------+-----------+-----------+
| pointer 0 | pointer 1 | pointer 2 | pointer 3 | pointer 4 |
+-----------+-----------+-----------+-----------+-----------+
|
V
+--------------+
| some integer |
+--------------+

x[0]产生“数组地址”,
x[0][0]产生“指针0”,
x[0][0][0]产生“某个整数”。

我相信,现在应该很明显了,为什么他们都是不同的。


上面的内容对于基本的理解来说已经足够接近了,这就是为什么我按照自己的方式来写的原因。然而,正如 Hacks 正确地指出的那样,第一行并不是100% 精确的。下面是所有的细节:

从 C 语言的定义来看,x[0]的值是整数指针的整个数组。然而,数组在 C 语言中是不能真正用来做任何事情的。你总是要么操纵他们的地址,要么操纵他们的元素,而不是整个数组作为一个整体:

  1. 您可以将 x[0]传递给 sizeof操作员。但这并不是真正意义上的值的使用,它的结果只取决于类型。

  2. 您可以获取它的地址,它产生 x的值,即“数组的地址”与类型 int*(*)[5]。换句话说: &x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. 所有其他情况中,x[0]的值将衰减为指向数组中第一个元素的指针。也就是说,一个值为“ address of array”且类型为 int**的指针。效果与将 x强制转换为 int**类型的指针相同。

由于情况3中的数组指针衰减。x[0]的所有使用最终导致一个指向指针数组开始的指针; 调用 printf("%p", x[0])将打印标记为“数组地址”的内存单元的内容。

在 C + + 中有一个原则,那就是: 变量的声明准确地指示了使用变量的方式。考虑一下你的声明:

int *(*x)[5];

可以改写为(为了更清楚) :

int *((*x)[5]);

根据原则,我们有:

*((*x)[i]) is treated as an int value (i = 0..4)
→ (*x)[i] is treated as an int* pointer (i = 0..4)
→ *x is treated as an int** pointer
→ x is treated as an int*** pointer

因此:

x[0] is an int** pointer
→ x[0][0] = (x[0]) [0] is an int* pointer
→ x[0][0][0] = (x[0][0]) [0] is an int value

这样你就能找出区别了。

首先我要说的是

X [0] = * (x + 0) = * x;

X [0][0] = * (* (x + 0) + 0) = * * x;

X [0][0] = * (* (* (x + 0) + 0) = * * * x;

所以 * x ≠ * * x ≠ * * * x

从下面的图片中,一切都清楚了。

  x[0][0][0]= 2000


x[0][0]   = 1001


x[0]      = 10

enter image description here

这只是一个例子,其中值 X [0][0] = 10

X [0][0][0]的地址是 1001

该地址存储在 X [0][0] = 1001

X [0][0]的地址是 二零零零年

地址存储在 X [0] = 2000

所以 X [0][0][0] X [0][0] X [0]

.

编辑

程序1:

{
int ***x;
x=(int***)malloc(sizeof(int***));
*x=(int**)malloc(sizeof(int**));
**x=(int*)malloc(sizeof(int*));
***x=10;
printf("%d   %d   %d   %d\n",x,*x,**x,***x);
printf("%d   %d   %d   %d   %d",x[0][0][0],x[0][0],x[0],x,&x);
}

输出

142041096 142041112 142041128 10
10 142041128 142041112 142041096 -1076392836

程序2:

{
int x[1][1][1]={10};
printf("%d   %d   %d   %d \n ",x[0][0][0],x[0][0],x[0],&x);
}

输出

10   -1074058436   -1074058436   -1074058436

如果你从现实世界的角度来看这些数组,它们看起来是这样的:

ABc0是一个装满板条箱的集装箱。
ABc0是一个单独的板条箱,里面装满了鞋盒,放在集装箱里。
ABc0是箱子里的一个鞋盒在集装箱里。

即使它是集装箱里唯一一个箱子里的鞋盒,它仍然是一个鞋盒,而不是一个集装箱

作为 p一个指针: 你正在与 p[0][0]堆叠解引用,这相当于 *((*(p+0))+0)

C 语言中的引用(&)和解引用(*)表示法:

p == &p[0] == &(&p[0])[0] == &(&(&p[0])[0])[0])

相当于:

p == &*(p+0) == &*(&*(p+0))+0 == &*(&*(&*(p+0))+0)+0

看,& * 可以被重构,只需删除它:

p == p+0 == p+0+0 == p+0+0+0 == (((((p+0)+0)+0)+0)+0)

其他的答案都是正确的,但是没有一个强调 三者都可能包含相同的值这个概念,所以在某种程度上它们是不完整的。

不能从其他答案中理解这一点的原因是,所有的插图,尽管在大多数情况下是有帮助的,而且肯定是合理的,但是没有涵盖指针 x指向自己的情况。

这很容易构建,但显然有点难以理解。在下面的程序中,我们将看到如何强制三个值相同。

注意: 这个程序中的行为是未定义的,但是我把它放在这里纯粹是作为一个有趣的演示,指向 可以,但是指向 不应该

#include <stdio.h>


int main () {
int *(*x)[5];


x = (int *(*)[5]) &x;


printf("%p\n", x[0]);
printf("%p\n", x[0][0]);
printf("%p\n", x[0][0][0]);
}

在 C89和 C99中无需警告即可编译,输出如下:

$ ./ptrs
0xbfd9198c
0xbfd9198c
0xbfd9198c

有趣的是,这三个值都是相同的。但这并不奇怪! 首先,让我们分解这个程序。

我们声明 x作为一个指向一个包含5个元素的数组的指针,其中每个元素的类型都是指向 int 的指针。这个声明在运行时堆栈上分配4个字节(或更多,这取决于您的实现; 在我的机器上,指针是4个字节) ,因此 x指的是一个实际的内存位置。在 C 语言家族中,x的内容只是垃圾,是以前使用该位置时遗留下来的内容,因此 x本身不指向任何地方 & mash; 当然也不指向已分配的空间。

所以,很自然地,我们可以把变量 x的地址放在某个地方,这就是我们要做的。但是我们继续,把它放到 x 中。因为 &xx有不同的类型,所以我们需要进行强制转换,这样就不会收到警告。

内存模型应该是这样的:

0xbfd9198c
+------------+
| 0xbfd9198c |
+------------+

因此,位于地址 0xbfd9198c的4字节内存块包含对应于十六进制值 0xbfd9198c的位模式。很简单。

接下来,我们打印出这三个值。其他的答案解释了每个表达式所指的内容,所以现在这种关系应该很清楚了。

我们可以看到,值是相同的,但只是在一个非常低的层次意义上... ... 它们的位模式是相同的,但与每个表达式相关联的类型数据意味着它们解释的值是不同的。 例如,如果我们使用格式字符串 %d打印出 x[0][0][0],我们会得到一个巨大的负数,所以实际上“值”是不同的,但位模式是相同的。

这实际上非常简单... 在图中,箭头只是指向相同的内存地址,而不是指向不同的内存地址。然而,虽然我们能够强迫一个预期的结果出未定义行为,它只是一个未定义的。这不是生产代码,而只是为了完整性而进行的演示。

在合理的情况下,您将使用 malloc创建5个 int 指针的数组,并再次创建该数组中指向的 int。malloc总是返回一个唯一的地址(除非内存不足,在这种情况下返回 NULL 或0) ,所以您永远不必担心这样的自引用指针。

希望这就是你想要的完整答案。你不应该期望 x[0]x[0][0]x[0][0][0]是相等的,但是如果是被迫的,它们可能是相等的。如果有什么事情超过了你的头,让我知道,我可以澄清!

int *(*x)[5]的类型是 int* (*)[5],即指向一个由5个指向 int 的指针组成的数组的指针。

  • x是第一个包含5个指向 int (类型为 int* (*)[5]的地址)的指针数组的地址
  • x[0]是指向 int 的第一个5个指针数组的地址(与类型 int* [5]相同的地址)(偏移地址 x 由 0*sizeof(int* [5])表示,即 index * size-of-type-being-pointed-to 和 dereference)
  • x[0][0]是数组中指向 int 的第一个指针(与 int*类型的地址相同)(偏移地址 x 由 0*sizeof(int* [5])和解引用,然后由 0*sizeof(int*)和解引用)
  • x[0][0][0]是指向 int 的指针所指向的第一个整型数(偏移地址 x 由 0*sizeof(int* [5])和解引用和偏移该地址由 0*sizeof(int*)和解引用和解引用和偏移该地址由 0*sizeof(int)和解引用)

int *(*y)[5][5][5]的类型是 int* (*)[5][5][5],即一个指向3d 数组的指针,该数组由5x5x5个指针组成

  • x是第一个3d 数组的地址,该数组由5x5x5指向类型为 int*(*)[5][5][5]的 int 的指针组成
  • x[0]是指向 int 的第一个3d 数组的地址(偏移地址 x 通过 0*sizeof(int* [5][5][5])和解引用)
  • x[0][0]是第一个2d 数组的地址,该数组由5x5个指向 int 的指针组成(偏移地址 x 为 0*sizeof(int* [5][5][5]),取消引用然后偏移地址 0*sizeof(int* [5][5]))
  • x[0][0][0]是第一个由5个指向 int 的指针组成的数组的地址(偏移地址 x 为 0*sizeof(int* [5][5][5]),解引用为 0*sizeof(int* [5][5]),偏移地址为 0*sizeof(int* [5]))
  • x[0][0][0][0]是数组中指向 int 的第一个指针(偏移地址 x 由 0*sizeof(int* [5][5][5])和解引用和偏移该地址由 0*sizeof(int* [5][5])和偏移该地址由 0*sizeof(int* [5])和偏移该地址由 0*sizeof(int*)和解引用)
  • x[0][0][0][0][0]是通过指向 int 的指针指向的第一个整型数(偏移地址 x 由 0*sizeof(int* [5][5][5])和解引用和该地址由 0*sizeof(int* [5][5])和偏移该地址由 0*sizeof(int* [5])和偏移该地址由 0*sizeof(int*)和解引用和偏移该地址由 0*sizeof(int)和解引用)

至于阵列衰减:

void function (int* x[5][5][5]){
printf("%p",&x[0][0][0][0]); //get the address of the first int pointed to by the 3d array
}

这相当于传递 int* x[][5][5]int* (*x)[5][5],也就是说它们都衰变为后者。这就是为什么在函数中使用 x[6][0][0]时不会得到编译器警告,但是在使用 x[0][6][0]时会得到警告,因为大小信息保留了下来

void function (int* (*x)[5][5][5]){
printf("%p",&x[0][0][0][0][0]); //get the address of the first int pointed to by the 3d array
}
  • x[0]是指向 int 的第一个5x5x5的3d 数组的地址
  • x[0][0]是5x5指向 int 的第一个2d 数组的地址
  • x[0][0][0]是第一个指向 int 的5个指针数组的地址
  • x[0][0][0][0]是数组中指向 int 的第一个指针
  • x[0][0][0][0][0]是指向 int 的指针指向的第一个 int

在最后一个示例中,使用 *(*x)[0][0][0]而不是 x[0][0][0][0][0]在语义上要清楚得多,这是因为这里的第一个和最后一个 [0]是 解释为指针解引用,而不是指向多维数组的索引,因为。但是它们是相同的,因为 (*x) == x[0]与语义无关。你也可以使用 *****x,它看起来像是对指针解引用5次,但实际上它的解释是完全一样的: 偏移量,解引用,解引用,数组中的2个偏移量和解引用,纯粹是因为你要应用操作的类型。

从本质上讲,当你从 [0]* a 到非数组类型时,它是由于 *(a + 0)的优先顺序而产生的偏移和解引用。

当你从 [0]*到一个数组类型时,它是一个偏移量,然后是一个幂等的解引用(解引用被编译器解析以产生相同的地址-它是一个幂等操作)。

当您使用 [0]*类型的1d 数组类型时,它是一个偏移量,然后是一个解引用

如果 [0]**是2d 数组类型,那么它只是一个偏移量,即一个偏移量,然后是一个幂等的解引用。

如果你是 [0][0][0]或者 ***一个3d 数组类型,那么它是一个偏移 + 幂等的解引用,然后是偏移 + 幂等的解引用,然后是偏移 + 幂等的解引用,然后是解引用。只有在完全去除数组类型时才会发生真正的解引用。

对于 int* (*x)[1][2][3]的示例,类型按顺序展开。

  • x的类型是 int* (*)[1][2][3]
  • *x具有 int* [1][2][3]类型(偏移量0 + 幂等解引用)
  • **x具有 int* [2][3]类型(偏移量0 + 幂等解引用)
  • ***x具有 int* [3]类型(偏移量0 + 幂等解引用)
  • ****x的类型为 int*(偏移量0 + 解引用)
  • *****x具有 int类型(偏移量0 + 解引用)