сбивает с толку указатель и массив - PullRequest
7 голосов
/ 09 августа 2010

У нас есть

 int a[5]={10, 20, 30, 40, 50};

Я хотел бы знать, как работают следующие два сегмента кода?

 int *ptr = (int *)(&a+1);
 int *t = (int *)(&a -1);

Если у нас есть

 printf("%d  %d  %d \n", *(a+1), *(ptr-1), *(t+1));

Чтодолжен быть результат?

Ответы [ 4 ]

6 голосов
/ 09 августа 2010

Поскольку тип a равен массив-5- int с , это означает, что тип &a равен указатель на массив-5- int s .

Когда вы добавляете или вычитаете 1 из указателя, вы просите его указать на следующий или предыдущий объект этого типа в памяти.Так &a+1 создает указатель на массив-5- int сразу после a в памяти (которого не существует), а &a-1 создает указатель на массив-5-int непосредственно перед a в памяти (которая также не существует).В памяти это выглядит так (где каждая ячейка представляет один int):

Address:    &a-1                      &a                      &a+1
Contents:  | ?  | ?  | ?  | ?  | ?  | 10 | 20 | 30 | 40 | 50 | ?  | ?  | ?  | ?  | ?  |

Когда в выражении *(a+1) используется a, он преобразуется в указатель на свой первый элемент- поэтому указатель на int указывает на значение 10.Добавление одного к нему делает указатель, указывающий на следующие int - a+1 точек на значение 20.*(a+1) затем выбирает это значение, поэтому первое напечатанное число равно 20.

Поскольку ptr также является указателем на int, это означает, что ptr - 1 создает указатель на int непосредственно перед ptr - в этом случае он будет указывать на 50. Таким образом, второе напечатанное число - 50.

Аналогично, t + 1 создает указатель на int сразу после t - в данном случае это вторая ? на приведенной выше диаграмме.Это неинициализированное значение - оно может напечатать что угодно или даже вызвать сбой программы.

Address:    &a-1                      &a                       &a+1
            t    t+1                  a   a+1            ptr-1 ptr
Contents:  | ?  | ?  | ?  | ?  | ?  | 10 | 20 | 30 | 40 | 50  | ?  | ?  | ?  | ?  | ?  |
4 голосов
/ 09 августа 2010

Все проблемы возникают из-за использования &a, который является указателем на «массив из пяти целых чисел», так что арифметика указателей (когда вы думаете с точки зрения адресов) «масштабируется» на sizeof(a) ( например, 20, если int - 4 байта, и компилятору не требуется заполнение для выравнивания - разумные гипотезы, хотя, конечно, далеко не определенные.

Итак, после

int *ptr = (int *)(&a+1);
int *t = (int *)(&a -1);

ptr - указатель на int по адресу памяти "sizeof (a) больше, чем адрес a", и t аналогично для "sizeof (a) меньше, чем адрес a". Поэтому ...:

 printf("%d  %d  %d \n", *(a+1), *(ptr-1), *(t+1));

Каким должен быть результат?

Вполне возможно, нарушение сегментации, в противном случае 20, за которым следуют два совершенно произвольных целочисленных значения. Поскольку ptr и t являются указателями на int, адресное арифметическое масштабирование для их -1 и +1 не не компенсирует то, что было сделано для &a (масштабирование с точки зрения памяти адреса указываются на sizeof(int), а не sizeof(a)!), поэтому ptr-1 и t+1 указывают на (предположительно ;-) int с, что соответственно "через несколько int с после окончания a "и" за несколько int с до начала a ".

Нет никакого способа узнать, есть ли на этих произвольных адресах какая-либо память, к которой процессу разрешено обращаться (откуда возможны нарушения сегментации), и, если какая-либо доступная память есть там, какая ее содержимое, «воспринимаемое как int», возможно, может быть.

Редактировать : @caf указывает, что ptr - 1 является не недопустимым - он правильно указывает на последний элемент a; поэтому вывод (если нет ошибки сегментации, которую @NullUserException считает маловероятной, но с этим мы не согласны ;-) начнется с 20 50 перед третьим, «произвольным» мусором. Точка, согласно стандарту C, является действительной для вычисления (хотя и не для использования) указателя «только один конец» массива, и размера массива должен точно соответствовать длине этого массива по размеру его элементов (заполнение допускается для типа элемента, если необходимо, и если да, то оно отображается в собственном размере элемента, но не для массива в целом). Тонкий, но важный; -).

0 голосов
/ 09 августа 2010

«Каким должен быть результат»?

В следующий раз, когда вы захотите узнать, что должен делать крошечный фрагмент кода, проверьте это: http://ideone.com/4fCud

Результат, который я получилиз этого было:

20 50 134520820

Редактировать:

Когда вы запускаете программу, смотрите вывод, как этоти задаешь себе вопрос: "откуда взялась эта ценность?"возможно, вы столкнулись с неопределенным поведением.

В этом случае третье значение не указывает на то, куда вы могли бы подумать.Это чтение неинициализированной памяти (скорее всего), памяти, принадлежащей коду, находящемуся в пространстве вашего процесса, но за пределами вашей программы (загруженная вами библиотека или среда выполнения C), или памяти, которая просто не имеет ничего общего с этой программой (меньшескорее всего, из-за защищенной памяти).

0 голосов
/ 09 августа 2010

Давайте посмотрим на это по частям.

&a означает адрес a.Таким образом, он получает адрес целого числа 10.
&a+1 - следующий указатель из этого.Так что это вещь хранится после переменной a в памяти.Плохая идея.
&a-1 - это вещь, хранящаяся до a в памяти.Опять же, плохая идея.

*(a+1) - это вещь в месте, на которое указывает плюс одно целое число.Это будет a[1], или 20.
*(ptr-1) - это a, потому что ptr - это &a+1, поэтому ptr-1 - это &a.Это значение указателя a.Печать его как %d является ошибкой.Если вы скажете **(ptr-1), вы получите более значимое значение 10 от printf.
*(t+1) также равно a, как указано выше, но с включенными плюсами и минусами.

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