Почему x [0]! = X [0] [0]! = X [0] [0] [0]?


149

Я немного изучаю 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]?


58
x[0], x[0][0]И x[0][0][0]имеют разные типы. Их нельзя сравнивать. Что вы имеете в виду !=?
Болов

4
@celticminstrel они не одинаковы: int **x[5]это массив из 5 элементов. Элемент - указатель на указатель на int`
болов

5
@celticminstrel int** x[5]будет массивом из пяти указателей, которые указывают на указатели, указывающие на int. int *(*x)[5]это указатель на массив из пяти указателей, которые указывают на int.
Emlai


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

Ответы:


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]содержит адрес, intкоторый является0x100 .
  • x[0][0][0]содержит intзначение 10.

Так, x[0]равно &x[0][0]и , следовательно, &x[0][0] != x[0][0].
Следовательно, x[0] != x[0][0] != x[0][0][0].


Эта диаграмма немного сбивает меня с толку: она 0x100должна появиться сразу слева от поля 10, так же, 0x500как и слева от поля. Вместо того, чтобы быть далеко налево и внизу.
ММ

@MattMcNabb; Я не думаю, что это должно сбивать с толку, но изменения в соответствии с вашим предложением для большей ясности.
хак

4
@haccks - Мое удовольствие :) Причина, по которой эта диаграмма великолепна, в том, что вам даже не нужно объяснение, которое вы дали, которое следует за ней. Эта диаграмма сама по себе говорит о том, что она уже отвечает на вопрос. Текст, который следует, является просто бонусом.
Rayryeng

1
Вы также можете использовать Yed, программное обеспечение для построения диаграмм. Это очень помогает мне в организации моих мыслей
rpax

@GrijeshChauhan Я использую asciiflow для комментариев кода, да для презентаций :)
rpax

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

41
@ Leo91 x[0][0]есть (x[0])[0], то есть *((*(x+0))+0)нет *(x+0+0). Разыменование происходит до второго [0].
Emlai

4
@ Leo91 x[0][0] != *(x+0+0)просто так x[2][3] != x[3][2].
Озг

@ Leo91 Второй комментарий, который вы "получили сейчас", был удален. Вы что-то не понимаете (что лучше объяснить в ответе) или это не ваша работа? (некоторые люди любят удалять комментарии без особого информативного содержания)
deviantfan

@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% точна. Итак, вот и все мелкие детали:

Из определения языка Си значение x[0]представляет собой целый массив целочисленных указателей. Тем не менее, массивы - это то, с чем вы не можете ничего сделать в C. Вы всегда манипулируете либо их адресом, либо их элементами, а не всем массивом в целом:

  1. Вы можете перейти x[0]к sizeofоператору. Но на самом деле это не использование значения, его результат зависит только от типа.

  2. Вы можете взять его адрес, который дает значение x, то есть «адрес массива» с типом int*(*)[5]. Другими словами:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x

  3. Во всех других контекстах значение x[0]будет распадаться на указатель на первый элемент в массиве. То есть указатель со значением «адрес массива» и типом int**. Эффект такой же, как если бы вы навели xуказатель типа int**.

Из-за затухания указателя массива в случае 3. все случаи использования в x[0]конечном итоге приводят к указателю, который указывает начало массива указателей; вызов printf("%p", x[0])напечатает содержимое ячеек памяти, помеченных как «адрес массива».


1
x[0]это не адрес массива.
хак

1
@haccks Да, следуя букве стандарта, x[0]это не адрес массива, а сам массив. Я добавил подробное объяснение этого и того, почему я написал, что x[0]это «адрес массива». Я надеюсь тебе это понравится.
cmaster - восстановить монику

потрясающие графики, которые объясняют это на отлично!
MK

«Однако массивы - это то, с чем вы не можете ничего сделать в Си». -> Пример счетчика: printf("%zu\n", sizeof x[0]);сообщает размер массива, а не размер указателя.
chux - Восстановить Монику

@ chux-ReinstateMonica И я продолжил: «Вы всегда манипулируете либо их адресом, либо их элементами, а не целым массивом целиком», а затем пунктом 1 перечисления, в котором я говорю о влиянии sizeof x[0]...
cmaster - восстановить монику

18
  • x[0]разыменовывает самый внешний указатель ( указатель на массив размером 5 указателя на int) и приводит к массиву размера 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];

тогда выражение x[0]является массивом типа int *[5]. Учтите, что выражение x[0]эквивалентно выражению *x. То есть разыменование указателя на массив мы получаем сам массив. Позвольте обозначить это как у, то есть у нас есть объявление

int * y[5];

Выражение x[0][0]эквивалентно y[0]и имеет тип int *. Позвольте обозначить это как г, то есть у нас есть объявление

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;

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

введите описание изображения здесь

Это просто пример, где значение 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

@haccks Если он использует только указатели, этот ответ будет правильным. В разделе адреса будут некоторые изменения при использовании массива
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 дюймов, а не указатель. (в большинстве случаев он может затухать до указателя, но здесь важно различение).
ММ

Хорошо, но вы должны сказать: x [0] это массив из 5 указателей int *
Nghia Bui

Чтобы дать правильный вывод для @MattMcNabb: *(*x)[5]есть int, то (*x)[5]есть int *, то *xесть (int *)[5], то и xесть *((int *)[5]). То xесть указатель на 5-массив указателей на int.
wchargin

2

Вы пытаетесь сравнить разные типы по значению

Если вы берете адреса, вы можете получить больше, чем вы ожидаете

Имейте в виду, что ваша декларация имеет значение

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

позволявшее сравнение вы хотите, так как y, y[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 [0]! = X [0] [0]! = X [0] [0] [0]», когда x - указатель, верно? Я пытался показать ему, что он может быть пойман в ловушку с помощью C разыменования (*), когда он сложил [0]. Итак, это попытка показать ему правильную запись, чтобы 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 и снова для создания целых чисел, на которые указывают в этом массиве.mallocвсегда возвращает уникальный адрес (если только у вас недостаточно памяти, в этом случае он возвращает NULL или 0), поэтому вам никогда не придется беспокоиться о самоссылочных указателях, подобных этому.

Надеюсь, это полный ответ, который вы ищете. Вы не должны ожидать x[0], x[0][0]и x[0][0][0]быть равными, но они могут быть, если вынуждены. Если что-то у вас над головой, дайте мне знать, чтобы я мог уточнить!


Я бы сказал, что другое странное использование указателей я видел когда-либо.
взломает

@haccks Да, это довольно странно, но когда вы разбиваете это, это так же просто, как и другие примеры. Это просто случай, когда битовые комбинации одинаковы.
Пураг

Ваш код вызывает неопределенное поведение. x[0]на самом деле не представляет действительный объект правильного типа
MM

@MattMcNabb это не определено, и я на самом деле очень ясно об этом. Я не согласен относительно типа. xявляется указателем на массив, поэтому мы можем использовать []оператор, чтобы указать смещение от этого указателя и разыменовать его. Что там странного? Результатом x[0]является массив, и C не будет жаловаться, если вы распечатываете его, используя, %pтак как это так или иначе реализовано ниже.
Пураг

И компиляция этого с -pedanticфлагом не выдает предупреждений, поэтому C хорошо работает с типами ...
Purag

0

Тип int *(*x)[5]- int* (*)[5]это указатель на массив из 5 указателей на целые.

  • xэто адрес первого массива из 5 указателей на целые числа (адрес с типом int* (*)[5])
  • x[0]адрес первого массива из 5 указателей на целые числа (тот же адрес, что и у типа int* [5]) (адрес смещения x, 0*sizeof(int* [5])т. е. индекс * размер-типа-на-указан-и-указатель)
  • 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*)и разыменование и смещение этого адреса на 0*sizeof(int)и разыменование)

Тип int *(*y)[5][5][5]: int* (*)[5][5][5]указатель на трехмерный массив 5x5x5 указателей на целые.

  • x адрес первого трехмерного массива указателей 5x5x5 на целые числа с типом int*(*)[5][5][5]
  • x[0]это адрес первого трехмерного массива указателей 5x5x5 на целые числа (адрес смещения x на 0*sizeof(int* [5][5][5])и разыменование)
  • x[0][0]является адресом первого двумерного массива указателей 5x5 на целые числа (адрес смещения x на 0*sizeof(int* [5][5][5])и разыменование затем смещает этот адрес на 0*sizeof(int* [5][5]))
  • x[0][0][0]является адресом первого массива из 5 указателей на целые числа (адрес смещения 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, на который указывает указатель на 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] адрес первого массива из 5х5х5 указателей на целые
  • x[0][0] адрес первого 2d массива указателей 5x5 на целые
  • x[0][0][0] адрес первого массива из 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 + 0)

Когда вы [0]или к типу массива , то это смещение , то идемпотент разыменования (The разыменовать разрешен компилятором , чтобы получить тот же адрес - это операция идемпотентная).**

Когда ты [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 + разыменование)
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.