Я немного изучаю 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]
?
x[0]
,x[0][0]
Иx[0][0][0]
имеют разные типы. Их нельзя сравнивать. Что вы имеете в виду!=
?int **x[5]
это массив из 5 элементов. Элемент - указатель на указатель на int`int** x[5]
будет массивом из пяти указателей, которые указывают на указатели, указывающие на int.int *(*x)[5]
это указатель на массив из пяти указателей, которые указывают на int.x[0] != x[0][0] != x[0][0][0]
значит? Это недопустимое сравнение в C ++. Даже если вы разделите его наx[0] != x[0][0]
иx[0][0] != x[0][0][0]
он все еще не действителен. Итак, что означает ваш вопрос?Ответы:
x
является указателем на массив из 5 указателейint
.x[0]
это массив из 5 указателей наint
.x[0][0]
это указатель наint
.x[0][0][0]
являетсяint
.Ты это видишь
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
как и слева от поля. Вместо того, чтобы быть далеко налево и внизу.согласно твоему посту,
что упрощено
Почему это должно быть равным?
Первый - это адрес какого-то указателя.
Второй - это адрес другого указателя.
И третий - это какая-то
int
ценность.источник
x[0][0]
есть(x[0])[0]
, то есть*((*(x+0))+0)
нет*(x+0+0)
. Разыменование происходит до второго[0]
.x[0][0] != *(x+0+0)
просто такx[2][3] != x[3][2]
.Вот расположение памяти вашего указателя:
x[0]
выдает "адрес массива",x[0][0]
выдает "указатель 0",x[0][0][0]
выдает "некоторое целое число".Полагаю, теперь должно быть очевидно, почему они все разные.
Вышесказанное достаточно близко для базового понимания, поэтому я написал так, как написал. Однако, как справедливо указывает хак, первая строка не на 100% точна. Итак, вот и все мелкие детали:
Из определения языка Си значение
x[0]
представляет собой целый массив целочисленных указателей. Тем не менее, массивы - это то, с чем вы не можете ничего сделать в C. Вы всегда манипулируете либо их адресом, либо их элементами, а не всем массивом в целом:Вы можете перейти
x[0]
кsizeof
оператору. Но на самом деле это не использование значения, его результат зависит только от типа.Вы можете взять его адрес, который дает значение
x
, то есть «адрес массива» с типомint*(*)[5]
. Другими словами:&x[0] <=> &*(x + 0) <=> (x + 0) <=> x
Во всех других контекстах значение
x[0]
будет распадаться на указатель на первый элемент в массиве. То есть указатель со значением «адрес массива» и типомint**
. Эффект такой же, как если бы вы навелиx
указатель типаint**
.Из-за затухания указателя массива в случае 3. все случаи использования в
x[0]
конечном итоге приводят к указателю, который указывает начало массива указателей; вызовprintf("%p", x[0])
напечатает содержимое ячеек памяти, помеченных как «адрес массива».источник
x[0]
это не адрес массива.x[0]
это не адрес массива, а сам массив. Я добавил подробное объяснение этого и того, почему я написал, чтоx[0]
это «адрес массива». Я надеюсь тебе это понравится.printf("%zu\n", sizeof x[0]);
сообщает размер массива, а не размер указателя.sizeof x[0]
...x[0]
разыменовывает самый внешний указатель ( указатель на массив размером 5 указателя на int) и приводит к массиву размера 5 указателя наint
;x[0][0]
разыменовывает внешний указатель и индексирует массив, в результате чего указатель наint
;x[0][0][0]
разыменовывает все, что приводит к конкретной стоимости.Между прочим, если вы когда-либо испытываете замешательство относительно того, что означают объявления такого типа, используйте cdecl .
источник
Пусть рассмотрит шаг за шагом выражениями
x[0]
,x[0][0]
иx[0][0][0]
.Как
x
определяется следующим образомтогда выражение
x[0]
является массивом типаint *[5]
. Учтите, что выражениеx[0]
эквивалентно выражению*x
. То есть разыменование указателя на массив мы получаем сам массив. Позвольте обозначить это как у, то есть у нас есть объявлениеВыражение
x[0][0]
эквивалентноy[0]
и имеет типint *
. Позвольте обозначить это как г, то есть у нас есть объявлениевыражение
x[0][0][0]
эквивалентно выражению,y[0][0]
которое в свою очередь эквивалентно выражениюz[0]
и имеет типint
.Итак, мы имеем
x[0]
имеет типint *[5]
x[0][0]
имеет типint *
x[0][0][0]
имеет типint
Таким образом, они являются объектами разных типов и, кстати, разных размеров.
Запустить например
источник
Первое, что я должен сказать, что
Из следующей картинки все ясно.
Это просто пример, где значение 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:
Вывод
Программа 2:
Вывод
источник
x[0]
не содержит адрес муравья. Это массив. Он исчезнет, чтобы указывать на свой первый элемент.Если бы вы рассматривали массивы с точки зрения реального мира, это выглядело бы так:
x[0]
грузовой контейнер, полный ящиков.x[0][0]
это один ящик, полный обувных коробок, внутри грузового контейнера.x[0][0][0]
это одна обувная коробка внутри ящика, внутри грузового контейнера.Даже если это была единственная обувная коробка в единственном ящике в грузовом контейнере, это все же обувная коробка, а не грузовой контейнер
источник
x[0][0]
будет ли один ящик, полный кусочков бумаги, на которых написано расположение обувных коробок?В C ++ есть принцип: объявление переменной точно указывает способ использования переменной. Рассмотрим вашу декларацию:
это можно переписать как (для большей ясности):
По принципу имеем:
Следовательно:
Таким образом, вы можете выяснить разницу.
источник
x[0]
это массив из 5 дюймов, а не указатель. (в большинстве случаев он может затухать до указателя, но здесь важно различение).*(*x)[5]
естьint
, то(*x)[5]
естьint *
, то*x
есть(int *)[5]
, то иx
есть*((int *)[5])
. Тоx
есть указатель на 5-массив указателей наint
.Вы пытаетесь сравнить разные типы по значению
Если вы берете адреса, вы можете получить больше, чем вы ожидаете
Имейте в виду, что ваша декларация имеет значение
позволявшее сравнение вы хотите, так как
y
,y[0]
,y[0][0]
,y[0][0][0]
будет иметь разные значения и типов , но один и тот же адресне занимает смежное пространство.
x
иx [0]
имеют одинаковый адрес, ноx[0][0]
иx[0][0][0]
каждый по разным адресамисточник
int *(*x)[5]
отличается отint **x[5]
Будучи
p
указателем: вы складываете разыменованияp[0][0]
, что эквивалентно*((*(p+0))+0)
.В C ссылка (&) и разыменование (*) обозначение:
Эквивалентно:
Посмотрите, что & * можно изменить, просто удалив его:
источник
p == p
.&(&p[0])[0]
отличается отp[0][0]
Другие ответы верны, но ни один из них не подчеркивает идею о том, что все три могут содержать одно и то же значение и , и поэтому они в некотором роде неполные.
Причина, по которой это не может быть понято из других ответов, заключается в том, что все иллюстрации, хотя они полезны и определенно обоснованы в большинстве случаев, не охватывают ситуацию, когда указатель
x
указывает на себя.Это довольно легко построить, но явно немного сложнее понять. В программе ниже мы увидим, как мы можем заставить все три значения быть идентичными.
ПРИМЕЧАНИЕ . Поведение в этой программе не определено, но я публикую его здесь просто как интересную демонстрацию того, что указатели могут делать, но не должны .
Он компилируется без предупреждений как в C89, так и в C99, и вывод будет следующим:
Интересно, что все три значения идентичны. Но это не должно быть сюрпризом! Во-первых, давайте разберем программу.
Мы объявляем
x
как указатель на массив из 5 элементов, где каждый элемент имеет указатель типа на int. Это объявление выделяет 4 байта в стеке времени выполнения (или больше в зависимости от вашей реализации; на моей машине указатели равны 4 байта), поэтомуx
ссылается на фактическую ячейку памяти. В семействе языков C содержимоеx
просто мусор, что-то, что осталось от предыдущего использования местоположения, поэтомуx
само по себе никуда не указывает - конечно, не на выделенное пространство.Итак, естественно, мы можем взять адрес переменной
x
и поместить его куда-нибудь, так что это именно то, что мы делаем. Но мы пойдем дальше и поместим это в сам x. Так как&x
имеет тип, отличный от тогоx
, нам нужно выполнить приведение, чтобы мы не получали предупреждений.Модель памяти будет выглядеть примерно так:
Таким образом, 4-байтовый блок памяти по адресу
0xbfd9198c
содержит битовую комбинацию, соответствующую шестнадцатеричному значению0xbfd9198c
. Достаточно просто.Далее мы распечатываем три значения. Другие ответы объясняют, к чему относится каждое выражение, поэтому теперь отношения должны быть ясными.
Мы можем видеть, что значения одинаковы, но только в смысле очень низкого уровня ... их битовые комбинации идентичны, но тип данных, связанный с каждым выражением, означает, что их интерпретируемые значения различны. Например, если мы распечатали с
x[0][0][0]
использованием строки формата%d
, мы получим огромное отрицательное число, поэтому «значения» на практике разные, но битовый шаблон одинаков.Это на самом деле очень просто ... на диаграммах стрелки просто указывают на один и тот же адрес памяти, а не на разные. Однако, хотя мы смогли навязать ожидаемый результат из неопределенного поведения, оно просто неопределенное. Это не рабочий код, а просто демонстрация для полноты картины.
В разумной ситуации вы будете использовать
malloc
для создания массива 5 указателей int и снова для создания целых чисел, на которые указывают в этом массиве.malloc
всегда возвращает уникальный адрес (если только у вас недостаточно памяти, в этом случае он возвращает NULL или 0), поэтому вам никогда не придется беспокоиться о самоссылочных указателях, подобных этому.Надеюсь, это полный ответ, который вы ищете. Вы не должны ожидать
x[0]
,x[0][0]
иx[0][0][0]
быть равными, но они могут быть, если вынуждены. Если что-то у вас над головой, дайте мне знать, чтобы я мог уточнить!источник
x[0]
на самом деле не представляет действительный объект правильного типаx
является указателем на массив, поэтому мы можем использовать[]
оператор, чтобы указать смещение от этого указателя и разыменовать его. Что там странного? Результатомx[0]
является массив, и C не будет жаловаться, если вы распечатываете его, используя,%p
так как это так или иначе реализовано ниже.-pedantic
флагом не выдает предупреждений, поэтому C хорошо работает с типами ...Тип
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)
разыменование)Что касается распада массива:
Это эквивалентно прохождению
int* x[][5][5]
или,int* (*x)[5][5]
т. Е. Все они распадаются на последние. Вот почему вы не получите предупреждение компилятора для использованияx[6][0][0]
в функции, но вы получите,x[0][6][0]
потому что информация о размере сохраняется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 + разыменование)источник