Будет ли прототипом a[1][2] быть это: int ** a?

#c

#c

Вопрос:

a[1][2] расширяется компилятором следующим образом: *( *(a 1) 2 ) . Итак, если a есть такой прототип: int **a ,

Предыдущее выражение должно быть объяснено следующим образом:

  1. Получите адрес a из таблицы символов. Обратите внимание, что это a указатель на указатель

  2. Теперь мы добавим его через 1 , тогда оно укажет на что-то рядом с тем, на что a указывает.

  3. Затем мы разыменовываем его. Я думаю, что здесь неопределенное поведение, поскольку мы не знаем, является ли a 1 допустимым, и мы произвольно обращаемся к нему.

  4. Хорошо, если нам повезет настолько, что мы успешно получим значение *(a 1) . Мы добавим это с помощью 2 .

  5. На этом шаге мы разыменовываем (*(a 1) 2 ) . Повезет ли нам сейчас?

Я прочитал это в Expert C Programming в главе 10. Правильно ли это?

Комментарии:

1. В чем именно заключается ваш вопрос? Выполнение a[1][2] может быть неопределенным поведением, если используется неправильно, или может быть полностью правильным, если о некоторых вещах известно a .

2. @aschepler : эх, это то, что меня смущает, на самом деле. Поскольку мы определяем это : int **a , мы не будем писать выражение, подобное этому : a[1][2] . но будет ли существовать вероятность того, что то, что написано в моем названии? Экспертное программирование на C дает положительный ответ.

Ответ №1:

Новый ответ после отредактированного вопроса:

Для a[1][2] того, чтобы быть допустимым, учитывая, что a has определяется как int **a; , оба из них должны быть истинными:

  • a должен указывать на первый из двух последовательных int * объектов;
  • Второй из этих int * объектов должен указывать на первый из трех последовательных int объектов.

Самый простой способ организовать это:

 int x[3];
int *y[2] = { 0, x };
int **a = y;
  

Оригинальный ответ:

Если выражение a[1][2] допустимо, то существует множество различных возможностей для типа a (даже пренебрегая такими определителями, как const ):

  1. type **a; (указатель на указатель на тип)
  2. type *a[n]; (массив из n указателей на тип)
  3. type (*a)[n]; (указатель на массив n типа)
  4. type a[m][n]; (массив из m массивов n типа)

То, как именно вычисляется выражение, зависит от того, какой из этих типов a на самом деле имеет.

Сначала a 1 вычисляется. Если a само по себе является указателем (либо случай 1, либо случай 3), то значение a загружается напрямую. Если a является массивом (случай 2 или случай 4), то загружается адрес первого элемента a (который идентичен адресу a самого).

Теперь этот указатель смещен на 1 объект того типа, на который он указывает. В случае 1 и случае 2 это было бы смещено на 1 «указатель на объект типа«; в случае 3 и случае 4 это было бы смещено на 1 «массив объекта n типа«, что совпадает с настройкой по n объектам типа.

Вычисленный (смещенный) указатель теперь разыменован. В случаях 1 и 2 результат имеет тип «указатель на тип«, в случаях 3 и 4 результат имеет тип «массив из n типов«.

Вычисляется Next *(a 1) 2 . Как и в первом случае, если *(a 1) является указателем, то значение используется напрямую (на этот раз, случаи 1 и 2). Если *(a 1) является массивом (случаи 3 и 4), то берется адрес первого элемента этого массива.

Результирующий указатель (который на данный момент всегда имеет тип «указатель на тип«) теперь смещен на 2 объекта типа. Теперь разыменован конечный указатель смещения, и извлекается объект типа.

Комментарии:

1. @carf: да, это то, что говорится в книге: четыре вероятности. первое — это то, что я считаю неправильным.

2. @larmbr: Смотрите обновление, я думаю, это может ответить на ваш вопрос?

Ответ №2:

Допустим, определение a выглядит примерно так:

 int a[2][2] = {{1, 2}, {3, 4}};
  

Вот как выглядит хранилище, в котором находится символ a :

 [ 1 ][ 2 ][ 3 ][ 4 ]
  

В C, когда вы выполняете арифметику над указателем, фактическая величина, на которую увеличивается или уменьшается значение указателя, зависит от размера типа, хранящегося в массиве. Тип, содержащийся в первом измерении a is int[2] , поэтому, когда мы просим C вычислить значение указателя (a 1), он принимает местоположение, названное a и увеличивает его на размер int[2] , что приводит к указателю, ссылающемуся на ячейку памяти, содержащую целочисленное значение [3]. Итак, да, когда вы разыменовываете этот указатель, а затем добавляете к нему 2, результатом является целое значение 5. Когда вы затем пытаетесь разыменовать это целое значение, это не имеет смысла.

Итак, теперь предположим, что массив содержит указатели:

 char const * one = "one",
             two = "two",
             three = "three",
             four = "four";
char const * a[2][2] = {{one, two}, {three, four}};
  

Добавьте 1 к a, а затем разыменуйте его, и вы получите указатель char, ссылающийся на строку «three». Добавьте к этому два, и вы получите указатель, ссылающийся на теперь более короткую строку «ree». Разыменуйте это, и вы получите значение char ‘r’, но только по чистой случайности вам удалось избежать ошибки защиты памяти.

Комментарии:

1. OP сказал, что a это указатель на указатель. Заданный int a[2][2]; , a не является указателем на указатель.

2. Извините, что означает OP? 😉

3. @larmbr: Оригинальный плакат. В данном случае, вы.