Это весело.
Помните, что элементы массива располагаются непрерывно - a
выглядит примерно так в памяти:
+---+
a: |'a'| a[0][0][0]
+---+
|'b'| a[0][0][1]
+---+
|'c'| a[0][0][2]
+---+
|'d'| a[0][1][0]
+---+
|'e'| a[0][1][1]
+---+
|'f'| a[0][1][2]
+---+
|'g'| a[0][2][0]
+---+
|'i'| a[0][2][1]
+---+
|'j'| a[0][2][2]
+---+
|'k'| a[1][0][0]
+---+
|'l'| a[1][0][1]
+---+
|'m'| a[1][0][2]
+---+
| 0 | a[1][1][0]
+---+
| 0 | a[1][1][1]
+---+
...
Поскольку вы предоставили меньше инициализаторов, чем размер массива для хранения (12 против18), остальные элементы инициализируются в 0 (это важно позже).
Во-первых, некоторый фон для массивов:
Если это не операнд sizeof
или унарный &
операторов или строковый литерал, используемый для инициализации массива символов в объявлении, выражение типа "массив N-элементов из T
" будет преобразовано ("распад") в выражениетипа "указатель на T
", а значением выражения будет адрес первого элемента массива.
Операция подстановки массива a[i]
имеет значение , определенное как *(a + i)
- с учетом начального адреса a
, смещения i
элементов (не байтов!) От этого адреса и разыменования результата.Это означает, что *a == *(a + 0) == a[0]
.
Итак, как это применимо к вашему коду?
Выражение a
имеет тип "2-элементный массив из 3-массив элементов трехэлементного массива char
".Если a
не является операндом операторов sizeof
или унарных &
, это выражение «затухает», чтобы напечатать «указатель на массив из 3 элементов из массива из 3 элементов char
», а также значениеВыражение - это адрес первого элемента массива - &a[0]
.
Выражение *a
разыменовывает этот указатель, и типом выражения является "массив из 3 элементов из массива из 3 элементов char
".Опять же, это выражение не является операндом sizeof
или унарным &
операндам, поэтому оно преобразуется в «указатель на массив из 3 элементов из char
», а значением является адрес первого элементамассив или &(*a)
.Начиная с *a == a[0]
, это также можно рассматривать как &a[0][0]
.
Выражение **a
разыменовывает результат *a
(у которого был тип "указатель на массив из 3 элементов char
")тип выражения - «3-элементный массив из char
».Опять же, это выражение не является операндом операторов sizeof
или унарных &
, поэтому оно «затухает» при вводе «указателя на char
».
Что, по совпадению, это то, что %s
ожидает.Спецификатор преобразования %s
ожидает, что его аргумент будет иметь тип char *
и будет указывать на первый в последовательности символов, за которой следует 0-значный терминатор.Помните ранее, когда я говорил, что остальные 6 элементов массива будут инициализированы в 0?Возможно, не имея значения, вы сохранили строку в a
.
Итак, вот почему вы получаете вывод, который вы делаете с **a
.Но почему *a
также работает?
Адрес массива совпадает с адресом первого элемента массива - &a[0] == &a[0][0] == &a[0][0][0]
(что вы можете видеть из рисунка ASCII выше). тип из *a
неправильный - char (*)[3]
вместо char *
- так что, строго говоря, поведение не определено, но его значение (по крайней мере, его логическое значение) являетсятакой же как **a
.Разные типы указателей могут иметь разные представления, поэтому может существовать платформа, на которой представление char (*)[3]
может отличаться от char *
, так что этот код не будет работать.Однако на платформах, таких как x86
, все типы указателей имеют одинаковое представление, поэтому значение *a
интерпретируется так же, как и значение **a
.