#c
#c
Вопрос:
a[1][2]
расширяется компилятором следующим образом: *( *(a 1) 2 )
. Итак, если a
есть такой прототип: int **a
,
Предыдущее выражение должно быть объяснено следующим образом:
-
Получите адрес
a
из таблицы символов. Обратите внимание, что этоa
указатель на указатель -
Теперь мы добавим его через
1
, тогда оно укажет на что-то рядом с тем, на чтоa
указывает. -
Затем мы разыменовываем его. Я думаю, что здесь неопределенное поведение, поскольку мы не знаем, является ли
a 1
допустимым, и мы произвольно обращаемся к нему. -
Хорошо, если нам повезет настолько, что мы успешно получим значение
*(a 1)
. Мы добавим это с помощью2
. -
На этом шаге мы разыменовываем
(*(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
):
type **a;
(указатель на указатель на тип)type *a[n];
(массив из n указателей на тип)type (*a)[n];
(указатель на массив n типа)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: Оригинальный плакат. В данном случае, вы.