Да, но синтаксис выглядит не очень хорошо
int(*f())[5] {
static int a[1][5] = { { 1, 2, 3, 4, 5 } };
return a;
}
По сути, это просто ваш a
, замененный f()
(функция). Использование typedef становится более читабельным
typedef int inner_array[5];
inner_array *f() {
// like before ...
}
Обратите внимание, что для обозначения абсолютного типа без имени необходимо написать int(*)[5]
. То есть вы просто стираете имя. (int*)[5]
недопустимый синтаксис.
Вы правы - вы не можете вернуть int**
, потому что это означает, что у вас есть указатель на указатель. Доступ с помощью f()[A][B]
будет считывать с адреса, который возвращает возвращенный указатель, а затем снова читать с адреса, указанного этим указателем по очереди. Но на самом деле указатель массива, который вы возвращаете, указывает только на один блок памяти, поэтому, если вы сделаете две косвенные ссылки, вы попытаетесь интерпретировать данные как указатели.
Наоборот, если вы вернете int(*)[5]
и введете f()[A][B]
, вы не будете читать значения с адреса, возвращаемого указателем. Вместо этого вы просто добавляете к адресу смещение A
и настраиваете тип от int(*)[5]
до int[5]
, чтобы получить массив, который ссылается на память по настроенному адресу. Затем следующий индекс будет снова скорректирован на B
, и, поскольку он работает с int*
, то (после того, как массив распался), больше не по указателю массива, он будет затем читать содержимое, сохраненное по скорректированному адресу, чтобы в итоге получить int
. Это важное отличие от указателей не-массивов и указателей массивов.
Вы можете поэкспериментировать с этим, если хотите. Рассмотрим эти два фрагмента. Один из них, вероятно, потерпит крах, но другой, вероятно, не будет (но оба приведут к неопределенному поведению, поэтому этого не следует делать в реальных программах)
int *pi = 0;
int(*pa)[5] = 0;
int i = *pi; // read int stored at null-pointer address!
int *a = *pa; // does not read any data from address, just adjusts the type!