p
- указатель на int
или на некоторые int
с.
Вы инициализируете его так, чтобы он указывал на адрес 17. (Это большая проблема по нескольким причинам, к которым мы вернемся.)
Затем вы добавляете 4 к нему. Очевидно, что sizeof(int)
на вашем компьютере равно 4, то есть int
занимает 4 байта (32 бита). Когда вы добавляете 4 к указателю int
, компилятор знает, что вы хотите, чтобы он указывал на значение 4 int
, поэтому компилятор добавляет 4 × 4 = 16 к адресу. Теперь p
указывает на адрес 33.
Затем вы «разыгрываете» (конвертируете) p
из указателя на long
. Так что теперь вместо указателя на адрес 33 это просто число 33.
Затем вы разыгрываете его снова, с long
до int
. Это в основном преобразует его из 33 в 33. (Если тип long
на вашей машине больше 4 байтов, это преобразование могло бы включать преобразование, скажем, из 64-битного значения 33 в 32-битное значение 33, то есть от 0x0000000000000021
до 0x00000021
.)
Затем вы печатаете последнее значение int
, используя %d
. Итак, вы видите число 33.
Теперь обычно говорят что-то вроде
int* p = (int*)17;
плохая идея, потому что у вас есть указатель со значением, которое, вероятно, не может быть использовано. Обычно одна вещь, которую вы делаете с указателями, - это манипулирование значениями, на которые они указывают. Но если бы вы сказали
printf("value pointed to by p is %d\n", *p);
В конечном итоге вы попытаетесь получить значение int
с адреса 17 в памяти. Но (а) у вас, вероятно, нет разрешения на чтение с адреса 17, и (б) 17 не кратно 4, поэтому ваш процессор может даже не захотеть получить оттуда int
, даже если вы действительно было разрешение. Так что этот код почти наверняка потерпит крах.
Но поскольку в вашем коде вы на самом деле никогда не пытаетесь что-либо делать с int
, на который гипотетически указывает p
(ни до, ни после добавления 4 к нему), ваш код, вероятно, - и едва ли - - - похоже, "работает".
С одной стороны, это плохой, непереносимый, едва определенный, если вообще не определенный код. Но опять же, это, вероятно, не предназначено, чтобы быть практичным (ясно, что никто не будет запускать его, чтобы выполнить какую-либо реальную работу). Так что, если все, что мы от него уберем, это урок о том, как работает арифметика указателей (в частности, как она автоматически масштабируется компилятором на основе размера объектов, на которые указывают указатели), возможно, нам не придется тратить слишком много времени используя свои многочисленные уродства и недостатки. (В ветке комментариев происходит небольшой спор о том, является ли код undefined или просто определяемым реализацией , но при условии, что вы не будете писать такой код на самом деле, вам не обязательно беспокоиться о различиях.)
Если вы хотите выучить более или менее тот же урок об арифметике указателей, но с использованием гораздо более разумной и в основном переносимой программы, попробуйте следующее:
#include <stdio.h>
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int main()
{
int *p = &a[3];
printf("p is %p and points to %d\n", p, *p);
printf("p+4 is %p and points to %d\n", p+4, *(p+4));
}
Здесь вместо вставки искусственного значения, такого как 17, в p
, мы инициализируем его, чтобы он указывал на фактический массив, как и указатели. И вместо того, чтобы конвертировать значение указателя в int
и печатать его с %d
, мы печатаем его с использованием %p
, который предназначен для печати указателей.
На моей машине эта программа печатает
p is 0x10f47a02c and points to 3
p+4 is 0x10f47a03c and points to 7
Как вы можете видеть, значения указателя не являются легко усваиваемыми маленькими числами, такими как 17 и 33, и мой аппарат решил напечатать их в шестнадцатеричном формате. Тем не менее, легко проверить, что 0x10f47a03c
- 0x10f47a02c
равно 0x10
, или 16. Мы добавили 4, что означает «сделать так, чтобы он указывал на 4 int
прошлое, на которое он указывает сейчас», а компилятор добавил 16.
[Сноска. Я сказал, что это «в основном портативно». Чтобы сделать его абсолютно портативным, вам нужно изменить printf
звонки на
printf("p is %p and points to %d\n", (void *)p, *p);
printf("p+4 is %p and points to %d\n", (void *)(p+4), *(p+4));
Строго говоря, %p
определен только для печати общих указателей на void
, а не произвольных других типов указателей. Итак, строго говоря, нам нужны эти (void *)
приведения, чтобы преобразовать значения указателя в правильный тип указателя для печати.]