Может кто-нибудь, пожалуйста, объясните мне следующее - PullRequest
0 голосов
/ 04 мая 2018

Может ли кто-нибудь объяснить мне, что происходит во время выполнения следующего фрагмента и почему он печатает 33? Спасибо,

#include <stdio.h>

void main(){
    int* p = (int*)17;
    printf("%d\n", (int)(long)(p+4));
}

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Что ж, результат 33 говорит мне, что ваша машина использует int с sizeof(int) == 4 (4 байта).

Разрешается преобразование целого числа в указатель с результатами, определяемыми реализацией. Вот что делает эта строка:

int* p = (int*)17;

теперь это указатель на int, а p+4 добавит в 4 раза размер int к указателю. 17 + 4*4 - это 33.

Ваша вторая строка

printf("%d\n", (int)(long)(p+4));

преобразует этот указатель в long, только чтобы преобразовать его в int сразу после этого. Преобразование в long здесь не имеет смысла.

Результатом всего этого является реализация, определенная , поскольку вы получите другие результаты на других машинах. Нет гарантии, что 17 является допустимым значением для указателя, поэтому преобразование может дать вам другое значение.

Но при условии, что 17 является допустимым значением для указателя, а int на вашем компьютере имеет размер 4, в результате получается 33.

0 голосов
/ 04 мая 2018

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 *) приведения, чтобы преобразовать значения указателя в правильный тип указателя для печати.]

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...