Объяснение наблюдаемых результатов
Результаты с массивами
После int arr[] = { 1, 2, 3, 4, 5 };
, arr
- это массив из пяти int
.
Затем &arr
является указателем на массив из пяти int
, а (&arr)[1]
будет массивом из пяти int
после arr
, если бы он был. В целях объяснения результатов, которые вы видели, давайте предположим, что на данный момент они есть. (Ниже я объясню без этого предположения.)
Как массив, (&arr)[1]
автоматически преобразуется в указатель на его первый элемент. 1 Так что (&arr)[1]
действует как указатель к первому int
в массиве из пяти int
, который следует arr
в памяти.
Аналогично, поскольку arr
является массивом из пяти int
, он преобразуется в указатель на его первый элемент. Так что arr
действует как указатель на первый int
в нем.
Когда вы печатаете их с помощью %d
, программа может напечатать адрес памяти, который является значением указателя, или часть Это. (%d
- неверный спецификатор преобразования для использования. См. Ниже.) Если это так, вы увидите фактические адреса как необработанные адреса памяти, обычно измеряемые в байтах.
В (&arr)[1] - arr
вы вычтете эти два указатели. Когда вы вычитаете два указателя в C, результатом будет количество элементов массива между двумя расположениями. Это не количество байтов. Стандарт C требует, чтобы реализация C предоставляла результат в виде числа элементов, даже если для выполнения преобразования из байтов в элементы массива необходимо выполнить деление.
Начиная с (&arr)[1]
(после автоматизации c преобразование) указывает на первые int
в массиве после массива из пяти int
, то есть arr
, а arr
(после преобразования) указывает на первые int
в arr
, они отличаются на пять int
, и поэтому результат равен пяти. Это то, что вы видели напечатанным, хотя вы должны использовать %td
для печати результата вычитания указателя, а не %d
.
Результаты с указателями
После int *arr; arr = (int*)malloc(10*sizeof(int));
, arr
это указатель на int
. Тогда &arr
является указателем на этот указатель, а (&arr)[1]
будет указателем после arr
, если бы он был. Когда вы печатаете необработанный адрес памяти arr
, вы увидите значение, возвращаемое malloc
. Однако, когда вы печатаете (&arr)[1]
, мы не знаем, что вы увидите - после arr
нет указателя, и ваша реализация C может вывести любое значение в памяти после arr
, но мы не знаем что это такое И, поскольку мы не знаем, каким будет значение (&arr)[1]
, мы не знаем, каким будет значение (&arr)[1] - arr
.
В вашем случае ptr = arr;
то же, что и выше, верно - нет правильного (&ptr)[1]
, поэтому мы не знаем, что будет напечатано. Возможная причина того, что «0» была напечатана, когда вы пытались это сделать, заключается в том, что компилятор поместил arr
в память сразу после ptr
, поэтому (&ptr)[1]
было arr
, а затем (&ptr)[1] - ptr
равно arr - ptr
, и это ноль, поскольку вы устанавливаете ptr
равным arr
.
Объяснение того, что говорит C Стандарт и исправление кода
Правильное использование указателей и обращение к объектам
Как указано выше, (&arr)[1]
относится к массиву из пяти int
после arr
, но такой массив не был определен. Из-за этого поведение (&arr)[1]
не определяется стандартом C. Следовательно, поведение printf("%d - %d: %d\n",(&arr)[1], arr, (&arr)[1] - arr);
не определяется стандартом C.
Вместо этого вы можете использовать (&arr + 1)
. Это указывает «один за пределы» массива arr
. То есть, он указывает, где будет следующий массив из пяти int
, если таковой будет. То же самое место было бы (&arr)[1]
, но (&arr+1)
определено, потому что выполнение указателя arithmeti c до «чуть дальше» объекта определяется стандартом C. (&arr)[1]
не определен, поскольку он не просто выполняет арифметику указателей c, но технически является ссылкой на несуществующий объект - технически это использование объекта, который не существует, даже если это немедленно преобразовано в указатель. Арифметика указателя c сразу после определения объекта, но использование гипотетического объекта сразу после определения одного объекта.
Другая альтернатива - &(&arr)[1]
. Это берет адрес (&arr)[1]
, который все еще будет неправильной ссылкой на объект, который не существует, за исключением того, что определение &
таково, что оно отменяет *
, который неявно присутствует в операторе индекса. Поэтому &(&arr)[1]
определено как (&arr + 1)
, хотя (&arr)[1]
не определено.
Правильные преобразования Printf
Чтобы напечатать указатель p
, используйте printf("%p", (void *) p);
.
Чтобы напечатать результат вычитания указателей p
и q
, используйте printf("%td", p-q);
.
Итак, правильный printf
для вашего первого случая может быть:
printf("%p - %p: %td\n", (void *) (&arr+1), (void *) &arr, (&arr+1) - &arr);
или:
printf("%p - %p: %td\n", (void *) (arr+5), (void *) arr, (arr+5) - arr);
В первом из них будут напечатаны адреса двух массивов и разница между ними в единицах массивов по пять int
. Эта разница будет одна.
Вторая напечатает адрес int
сразу за массивом arr
и адрес первого int
в arr
и разницу между ними в единицах int
. Эта разница будет одна. Два адреса в этом printf
будут такими же, как адреса в первом printf
, потому что они указывают на одно и то же место. (Примечание: стандарт C позволяет реализациям C иметь несколько способов представления указателей, поэтому возможно, что адреса при печати будут отображаться по-разному. Однако в большинстве распространенных реализаций C они будут кажутся идентичными.)
Ваш второй и третий случаи не могут быть легко исправлены, потому что они оба полагаются на использование значения объекта вне определенного единственного объекта (указатель). Мы могли бы исправить первый случай, потому что он использует только адрес объекта за пределами определенного объекта, и есть способы использовать этот адрес определенным образом. Поскольку во втором и третьем случаях делается попытка использовать значение несуществующего объекта, а не только его адрес, они изначально не определены.
Сноска
1 При использовании в выражении любой массив автоматически преобразуется в указатель на его первый элемент, за исключением случаев, когда он является операндом sizeof
, операндом унарного &
или строковым литералом, используемым для инициализации массива. Это преобразование происходит независимо от того, указан ли массив как arr
или как результат выражения как (&arr)[1]
.