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

时间:2015-07-05 14:28:41

标签: c++ c arrays pointers

我正在研究一些C ++,我正在用指针进行斗争。我明白通过声明我可以有3个级别的指针:

int *(*x)[5];

以便*x是一个指向5个元素数组的指针,这些元素是指向int的指针。 我也知道x[0] = *(x+0);x[1] = *(x+1)等等......

所以,鉴于上述声明,为什么x[0] != x[0][0] != x[0][0][0]

12 个答案:

答案 0 :(得分:258)

x是一个指向int的5个指针数组的指针 x[0]是指向int的5个指针的数组 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]

答案 1 :(得分:133)

x[0] != x[0][0] != x[0][0][0]

是根据你自己的帖子,

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

简化

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

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

答案 2 :(得分:49)

以下是指针的内存布局:

   +------------------+
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]产生“某个整数”。

我相信,现在应该是显而易见的,为什么它们都不同。

以上内容足够基本理解,这就是为什么我按照我编写它的方式编写它。然而,正如haccks正确指出的那样,第一行不是100%精确。所以这里有所有细节:

从C语言的定义来看,x[0]的值是整个整数指针数组。但是,数组是C中你无法做任何事情的东西。你总是操纵它们的地址或它们的元素,而不是整个数组:

  1. 您可以将x[0]传递给sizeof运营商。但这并不是对价值的实际使用,其结果仅取决于类型。

  2. 您可以使用其地址生成x的值,i。即具有类型int*(*)[5]的“数组地址”。换句话说:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. 所有其他上下文中,x[0]的值将衰减为指向数组中第一个元素的指针。也就是说,指针的值为“数组地址”和类型int**。效果与将x转换为int**类型指针的效果相同。

  4. 由于情况3中的数组指针衰减,x[0]的所有使用最终都会产生指向指针数组开头的指针;调用printf("%p", x[0])将打印标记为“数组地址”的存储单元格的内容。

答案 3 :(得分:18)

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

顺便说一句,如果您对这些声明的含义感到困惑,请使用cdecl

答案 4 :(得分:11)

让我们逐步考虑表达式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],而表达式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;

因此,它们是不同类型的对象,并且具有不同的大小。

运行例如

{{1}}

答案 5 :(得分:10)

首先我要说的是

  

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

     

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

     

x [0] [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] [0] = 10

的值

x [0] [0] [0] 的地址 1001

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

x [0] [0] 的地址 2000

该地址存储在 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 

答案 6 :(得分:7)

如果您要从现实世界的角度来看这些数组,那么它将如此显示:

x[0]是一个装满板条箱的货运集装箱 x[0][0]是货箱内装满鞋盒的单个箱子 x[0][0][0]是装箱内的单个鞋盒,位于货运集装箱内。

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

答案 7 :(得分:4)

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

所以你可以找出差异。

答案 8 :(得分:2)

成为p指针:您正在使用p[0][0]堆叠取消引用,这相当于*((*(p+0))+0)

在C引用(&amp;)和解除引用(*)表示法中:

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

相当于:

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

看一下,&amp; *可以重构,只需删除它:

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

答案 9 :(得分:1)

您正在尝试按值比较不同类型

如果您使用地址,您可能会获得更多您期望的地址

请记住,您的声明会有所作为

 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]各自位于不同的地址

答案 10 :(得分:1)

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

从其他答案中无法理解的原因是,所有插图虽然有用且在大多数情况下绝对合理,但未能涵盖指针x指向自身的情况。 / p>

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

注意:此程序中的行为未定义,但我在此处发布纯粹是为了指示可以做的事情的有趣演示,但< EM>不该&#39;吨

#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本身并不指向任何地方 - 当然不会分配空间。

所以,当然,我们可以将变量x的地址放在某个地方,这样我们就可以做到。但我们会继续把它放到x本身。由于&x的类型与x不同,我们需要进行投射,因此我们不会收到警告。

内存模型看起来像这样:

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

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

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

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

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

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

希望这是您正在寻找的完整答案。您不应期望x[0]x[0][0]x[0][0][0]相等,但如果强迫它们就可以。如果有什么事情发生了,请告诉我,我可以澄清一下!

答案 11 :(得分:0)

int *(*x)[5]的类型为int* (*)[5],即指向5个指向整数的指针的数组的指针。

  • x是指向int的5个指针的第一个数组的地址(类型为int* (*)[5]的地址)
  • x[0]指向int的5个指针的第一个数组的地址(类型为int* [5]的相同地址)(将地址x偏移0*sizeof(int* [5]),即index * type-of-being -指向和取消引用)
  • x[0][0]是数组中一个int的第一个指针(与类型int*相同的地址)(将地址x偏移0*sizeof(int* [5])并先解除引用再偏移0*sizeof(int*)和取消引用)
  • x[0][0][0]是指向int的指针所指向的第一个int值(将地址x偏移0*sizeof(int* [5])并用0*sizeof(int*)取消引用和偏移该地址,并使用$ {1}取消引用并偏移该地址。 0*sizeof(int)并取消引用)

int *(*y)[5][5][5]的类型为int* (*)[5][5][5],即指向5x5x5指向整数的3d数组的指针

  • x是指向类型为int*(*)[5][5][5]的int的5x5x5指针的第一个3d数组的地址。
  • x[0]是指向int的5x5x5指针的第一个3d数组的地址(将地址x偏移0*sizeof(int* [5][5][5])并取消引用)
  • x[0][0]是指向int的5x5指针的第一个2d数组的地址(将地址x偏移0*sizeof(int* [5][5][5]),然后取消引用,然后将该地址偏移0*sizeof(int* [5][5])
  • x[0][0][0]是指向int的5个指针的第一个数组的地址(将地址x偏移0*sizeof(int* [5][5][5]),然后将该地址解引用并偏移0*sizeof(int* [5][5]),然后将该地址偏移{{1 }})
  • 0*sizeof(int* [5])是指向数组中int的第一个指针(将地址x偏移x[0][0][0][0],然后将该地址解引用并偏移0*sizeof(int* [5][5][5]),然后将该地址偏移0*sizeof(int* [5][5])并将该地址偏移0*sizeof(int* [5])并取消引用)
  • 0*sizeof(int*)是指向int的指针所指向的第一个int值(将地址x偏移x[0][0][0][0][0],然后将该地址解引用并偏移0*sizeof(int* [5][5][5]),然后将该地址偏移{{ 1}},然后将该地址偏移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]是指向int的5x5x5指针的第一个3d数组的地址
  • 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 } 是指向int的5x5指针的第一个2d数组的地址
  • x[0]是5个指向整数的指针的第一个数组的地址
  • x[0][0]是指向数组中int的第一个指针
  • x[0][0][0]是指向int的指针所指向的第一个int

在最后一个示例中,使用x[0][0][0][0]而不是x[0][0][0][0][0]在语义上要清晰得多,这是因为这里的第一个和最后一个*(*x)[0][0][0]是 由于类型的原因,它被解释为指针取消引用,而不是多维数组的索引。但是它们是相同的,因为x[0][0][0][0][0]与语义无关。您还可以使用[0],它看起来像是对指针进行了5次解引用,但实际上它的解释完全相同:偏移量,解引用,解引用,数组中的2个偏移量和解引用,完全是因为您要将操作应用于的类型。

基本上,当您将(*x) == x[0]*****x的{​​{1}}设为非数组类型时,由于[0]的优先顺序,它是偏移量和取消引用。

当您将**到数组类型的*(a + 0)时,它是一个偏移量,然后是幂等解引用(该解引用由编译器解析以产生相同的地址–这是一个幂等运算。)

当您[0]*具有一维数组类型的类型时,它是一个偏移量然后是一个取消引用

如果您*[0]是2d数组类型,则它仅是偏移量,即偏移量和幂等解引用。

如果您*[0]是3D数组类型,则它是偏移+幂等解引用,然后是偏移+幂等解引用,然后是偏移+幂等解引用,然后是解引用。真正的取消引用仅在完全剥离数组类型时发生。

**为例,该类型按顺序展开。

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