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


149

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

int *(*x)[5];

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

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


58
x[0]x[0][0]并且x[0][0][0]具有不同的类型。它们无法比较。你是什么意思!=
bolov

4
@celticminstrel它们是不同的:int **x[5]是5个元素的数组。一个元素是一个指向int`的指针
bolov

5
@celticminstrel int** x[5]将是一个包含五个指针的数组,这些指针指向int。 int *(*x)[5]是指向五个指向int的指针的数组的指针。
埃莱2015年

5
@celticminstrel 左右规则螺旋规则C ber 英语和您成为三星级程序员的路上:)
bolov

5
@ Leo91:首先,这里有两个级别的指针,而不是三个。其次,是什么x[0] != x[0][0] != x[0][0][0]意思?在C ++中,这不是有效的比较。即使将其拆分为x[0] != x[0][0]x[0][0] != x[0][0][0]它仍然无效。那么,您的问题是什么意思?
AnT 2015年

Answers:


261

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]包含的地址,该地址int0x100
  • 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]


该图令我有些困惑:0x100应该立即显示在包含框的左侧,与显示在其框左侧的10方式相同0x500。而不是它在左边和下面。
MM

@MattMcNabb; 我不认为这应该引起混淆,但是请根据您的建议进行更改,以使其更加清晰。
c

4
@haccks-我的荣幸:)该图之所以很棒,是因为您甚至不需要跟随它的解释。该图本身已经可以回答问题,这是不言而喻的。后面的文字只是一个奖励。
rayryeng

1
您还可以使用yed(图表软件)。它有助于我整理思想
rpax 2015年

@GrijeshChauhan我将asciiflow用于代码的注释,用于演示的
yeD

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

根据您自己的帖子,

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

简化了

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

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


我不明白...如果x [0],x [0] [0],x [0] [0] [0]等于*(x + 0),*(x + 0 + 0) ,*(x + 0 + 0 + 0),为什么它们应该具有不同的地址?
Leo91 2015年

41
@ Leo91 x[0][0](x[0])[0],即*((*(x+0))+0)不是*(x+0+0)。取消引用发生在第二个之前[0]
埃莱2015年

4
@ Leo91 x[0][0] != *(x+0+0)就像x[2][3] != x[3][2]
OZG

@ Leo91您“立即获得”的第二条评论已删除。您是否不理解某些内容(可以在答案中得到更好的解释),或者这不是您的工作吗?(有些人喜欢删除内容不多的评论)
deviantfan 2015年

@deviantfan对不起,我听不懂你的意思。我理解答案以及许多有助于阐明概念的评论。
Leo91

50

这是指针的内存布局:

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

我相信,为什么现在它们都不同,现在应该很明显。


上面的内容对于基本的理解已经足够接近了,这就是为什么我以编写方式来编写它的原因。但是,正如鹰派正确指出的那样,第一行并不是100%精确。因此,这里有所有详细信息:

根据C语言的定义,的值x[0]是整数指针的整个数组。但是,数组是您在C中无法真正完成的事情。您总是操纵它们的地址或元素,而不要操纵整个数组:

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

  2. 您可以获取其地址,该地址产生类型的x,即“数组的地址” int*(*)[5]。换一种说法:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

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

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


1
x[0]不是数组的地址。

1
@haccks是的,遵循标准字母,x[0]不是数组的地址,而是数组本身。我添加了对此的深入解释,以及我为什么写它的原因,x[0]即“数组的地址”。我希望你喜欢它。
cmaster-恢复莫妮卡2015年

很棒的图表很好地解释了它!
MK

“但是,数组是您在C语言中无法真正完成的事情。” ->计数器示例:printf("%zu\n", sizeof x[0]);报告数组的大小,而不是指针的大小。
chux-恢复莫妮卡

@ chux-ReinstateMonica然后我继续说:“您总是操纵它们的地址或元素,而不是操纵整个数组”,然后是列举的第1点,在这里我谈到sizeof x[0]... 的作用
cmaster-恢复莫妮卡

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

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


11

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

x定义的以下方式

int *(*x)[5];

然后expression x[0]是type的数组int *[5]。考虑到表达式x[0]等于expression *x。那就是解引用指向数组的指针,我们得到数组本身。让我们像y一样表示它是一个声明

int * y[5];

表达式x[0][0]等价y[0]并具有type int *。让我们用z来表示它就是一个声明

int *z;

expression x[0][0][0]等效于expression y[0][0],而expression 等效于expression z[0]并具有type 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;

10

我首先要说的是

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

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

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

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

从下图可以清楚地看到所有情况。

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

  x[0][0]   = 1001

  x[0]      = 10

在此处输入图片说明

这只是一个示例,其中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 

3
您的回答有误导性。x[0]不包含蚂蚁地址。它是一个数组。它将衰减到指向其第一个元素的指针。

嗯...是什么意思?您的编辑就像是错误答案的小菜一碟。这没有道理
折断

@haccks如果仅使用指针,则此答案正确。使用Array时,地址部分会有所变化
apm

7

如果您要从现实世界的角度查看阵列,则它将显示为:

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

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


1
x[0][0]一个装满鞋盒位置的纸条装箱子会不会一个?
wchargin

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

因此,您可以找出差异。


1
x[0]是5个整数的数组,而不是指针。(在大多数情况下,它可能会衰减为指针,但是区别在这里很重要)。
MM

好的,但是您应该说:x [0]是5个指针的数组int *
Nghia Bui 2015年

要根据@MattMcNabb给出正确的推导:*(*x)[5]int,所以(*x)[5]int *,所以*x(int *)[5],所以x也是*((int *)[5])。也就是说,x是指向的5个数组的指针int
wchargin

2

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

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

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

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

将使你想要的比较,因为yy[0]y[0][0]y[0][0][0]将有不同的价值观和类型,但相同的地址

int **x[5];

不占用连续空间。

x并且x [0]具有相同的地址,但是x[0][0]x[0][0][0]分别位于不同的地址


2
int *(*x)[5]int **x[5]
MM

2

成为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)

您想在第一句话后显示所有内容吗?您在上有很多变化p == p&(&p[0])[0]p[0][0]
MM

这个家伙问为什么当x是指针时,为什么'x [0]!= x [0] [0]!= x [0] [0] [0]'对吗?我试图向他展示,当他堆叠[0]时,他可能会被C的取消引用(*)所困扰。因此,尝试向他显示使x等于x [0]的正确符号,并再次用&引用x [0],依此类推。
卢西亚诺

1

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

从其他答案中无法理解这一点的原因是,尽管所有插图在大多数情况下都是有用且绝对合理的,但它们并未涵盖指针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它本身不会指向任何地方-一定不会指向已分配的空间。

因此,自然地,我们可以获取变量的地址x并将其放在某个位置,这正是我们要做的。但是我们将继续将其放入x本身。由于&x类型不同于x,我们需要进行强制转换,因此我们不会收到警告。

内存模型如下所示:

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

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

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

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

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

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

希望这是您要寻找的完整答案。您不应期望x[0]x[0][0]x[0][0][0]相等,但是如果被迫,它们可能会相等。如果有什么事发生,请告诉我,以便我澄清!


我要说的是,我见过的另一种奇怪的指针用法。

@haccks是的,这很奇怪,但是当您分解它时,它与其他示例一样基本。恰好是位模式都相同的情况。
Purag 2015年

您的代码导致未定义的行为。x[0]实际上并不代表正确类型的有效对象
MM

@MattMcNabb它是未定义的,我对此非常清楚。我不同意这种类型。x是指向数组的指针,因此我们可以使用[]运算符从该指针中指定一个偏移量并对其取消引用。那里有什么奇怪的地方?结果x[0]是一个数组,如果您使用来打印,C不会抱怨,%p因为无论如何都是这样实现的。
Purag

并使用-pedantic标记进行编译不会产生任何警告,因此C适合以下类型...
Purag 2015年

0

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

  • x是5个指向ints的指针的第一个数组的地址(类型为的地址int* (*)[5]
  • x[0]指向int的5个指针的第一个数组的地址(与类型相同的地址int* [5])(将地址x偏移,0*sizeof(int* [5])即index *指向并取消引用的类型的大小)
  • x[0][0]是指向数组(与type相同的地址int*)中的int的第一个指针(偏移地址x by 0*sizeof(int* [5])和dereference,然后by 0*sizeof(int*)和dereference)
  • x[0][0][0]是指向int的指针所指向的第一个int(地址x由0*sizeof(int* [5])和取消引用和偏移,该地址由0*sizeof(int*)和取消引用,地址由和取消偏移0*sizeof(int)和取消引用)

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

  • x 是指向类型为int的int的5x5x5指针的第一个3d数组的地址 int*(*)[5][5][5]
  • x[0]是指向int的5x5x5指针的第一个3d数组的地址(偏移地址x by 0*sizeof(int* [5][5][5])和dereference)
  • 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 by 0*sizeof(int* [5][5][5])和取消引用并偏移该地址by 0*sizeof(int* [5][5])和偏移该地址by 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的指针所指向的第一个int(偏移地址x by 0*sizeof(int* [5][5][5])和取消引用并偏移该地址by 0*sizeof(int* [5][5])和偏移该地址by 0*sizeof(int* [5])和偏移该地址by 0*sizeof(int*)和取消引用并偏移该地址by 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] 是指向int的5x5指针的第一个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]*与一维数组类型的类型,然后它的一个偏移量,然后取消引用

如果你[0]还是**一个二维数组类型,那么它的偏移量,即只有一个偏移量,然后幂等间接引用。

如果你[0][0][0]还是***一个三维数组类型,那么它是一个偏移+幂等提领则偏移+幂等提领则偏移+幂等间接引用,然后取消引用。真正的取消引用仅在完全剥离数组类型时发生。

对于示例,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 +取消引用)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.