"Int (*) []" затухает в "int **" в параметре функции?

Я опубликовал этот question на programers.stackexchange ранее сегодня. Я всегда предполагал, что int (*)[] не распадается на int ** в функциональных параметрах, но я получил несколько ответов на мой вопрос, который предположил, что он это делает.

Я использовал int (*)[] в моих функциональных параметрах, но теперь я действительно запутался.

Когда я скомпилирую эту функцию с помощью gcc -std=c99 -pedantic -Wall

void function(int (*a)[])
{
    sizeof(*a);
}

Я получаю это сообщение об ошибке:

c99 -Wall -pedantic -c main.c -o main.o
main.c: In function ‘function’:
main.c:3:11: error: invalid application of ‘sizeof’ to incomplete type ‘int[]’ 
make: *** [main.o] Error 1

Из чего следует, что *a имеет тип int [], а не int *.

Может кто-нибудь объяснить, если такие вещи, как int (*)[], распадается на int ** в параметрах функции и дают мне некоторую ссылку (возможно, из стандартных документов), которая доказывает, почему это так.

Ответы

Ответ 1

Только типы массивов, преобразованные в указатель на его первый элемент при передаче функции. a имеет указатель на тип массива int, т.е. , он имеет тип указателя и, следовательно, не имеет преобразования.

Для прототипа

void foo(int a[][10]);
Компилятор

интерпретирует его как

void foo(int (*a)[10]);  

потому что a[] имеет тип массива. int a[][10] никогда не будет преобразован в int **a. Тем не менее, второй пара в том, что ответ является неправильным и вводящим в заблуждение.

В качестве параметра функции int *a[] эквивалентен int **, это связано с тем, что a имеет тип массива.

Ответ 2

int (*)[] является указателем на массив int.

В вашем примере *a может затухать до int*. Но sizeof(*a) не затухает; это существенно sizeof(int[]), что недопустимо.

a не может вообще затухать (это указатель).

Ответ 3

N1256 §6.7.5.3/p7-8

7 Объявление параметра как '' массива типа должно быть скорректирован на "квалифицированный указатель на тип, где квалификаторы типа (если есть) - это те, которые указаны в [ и ] для вывода типа массива. Если ключевое слово static также появляется в пределах [ и ] типа массива вывода, то для каждого вызова функции значение соответствующий фактический аргумент должен обеспечить доступ к первому элемент массива с по меньшей мере таким количеством элементов, как указано в размер.

8 Объявление параметра как возвращаемого типа функции должно отрегулировать на" указатель на возвращаемый тип функции", как в 6.3.2.1.

int (*)[] является "указателем на массив int". Это "массив типа"? Нет, это указатель. Является ли это функцией возвращаемого типа? Нет, это указатель. Поэтому он не корректируется.

Ответ 4

В случае int (*a)[], sizeof *a не работает по одной причине: для массива нет элемента. Без подсчета элементов размер не может быть рассчитан.

В результате этого любая арифметика указателя на a не будет работать, потому что она определяется в терминах размера объекта. Поскольку размер не определен для массива, вы не можете использовать арифметику указателя на самом указателе. Обозначение массива определяется в терминах арифметики указателя, поэтому sizeof a[0][0] (или любое выражение, включающее a[n], не будет работать, тогда как sizeof (*a)[0] будет.

Это означает, что вы можете сделать очень мало с указателем. Единственное, что разрешено:

  • разыменование указателя с помощью унарного оператора *
  • передача указателя на другую функцию (тип параметра функции должен быть массивом массивов или указателем на массив)
  • получение размера указателя (и выравнивания или типа, если ваш компилятор поддерживает их или их обоих)
  • назначение указателя на совместимый тип

Если ваш компилятор поддерживает массивы переменной длины (VLAs), и вы знаете размер, вы можете обойти проблему, просто добавив строку в начале тела функции, как в

void
foo (int (*a0)[], size_t m, size_t n)
{
  int (*a)[n] = a0;
  ...
}

Без VLA вы должны прибегнуть к какой-то другой мере.

Стоит отметить, что динамическое распределение не является фактором с int (*)[]. Массив массивов распадается на указатель на массив (как и здесь), поэтому они взаимозаменяемы при передаче их функции (sizeof и любые ключевые слова _Alignof или typeof являются операторами, а не функциями). Это означает, что указанный массив должен быть статически распределен: как только массив распадается на указатель, больше не происходит распад, поэтому вы не можете сказать, что указатель на массив (int (*)[]) совпадает с указателем на указатель (int **). В противном случае ваш компилятор с радостью передаст int [3][3] функции, которая принимает int ** вместо желаемого параметра формы int (*)[], int (*)[n], int [][n] или int [m][n].

Следовательно, даже если ваш компилятор не поддерживает VLA, вы можете использовать тот факт, что статически выделенный массив имеет все свои элементы, сгруппированные вместе:

void foo (int (*a0)[], size_t m, size_t n)
{
  int *a = *a0;
  size_t i, j;

  for (i = 0; i < m; i++)
    {
      for (j = 0; j < n; j++)
        {
          // Do something with `a[i * n + j]`, which is `a0[i][j]`.
        }
    }
  ...
}

Динамически распределенный одномерный массив, который используется в качестве двумерного массива, обладает теми же свойствами, поэтому это все еще работает. Только тогда, когда второе измерение динамически распределено, это означает, что цикл, подобный for (i = 0; i < m; i++) a[i] = malloc (n * sizeof *a[i]);, распределяет каждую подматрицу индивидуально, что этот принцип не работает. Это связано с тем, что у вас есть массив указателей (int *[] или int ** после распада массива), которые указывают на первый элемент массива в другом месте в памяти, а не на массив массивов, который сохраняет все элементы вместе.

Итак:

  • нет, int (*p)[] и int **q не могут использоваться одинаково. p является указателем на массив, что означает, что все элементы группируются вместе, начиная адрес, хранящийся в p. q является указателем на указатель, что означает, что элементы могут быть разбросаны по разным адресам, которые хранятся в q[0], q[1],..., q[m - 1].

  • sizeof *p не работает, потому что p указывает на массив с неизвестным количеством элементов. Компилятор не может рассчитать размер каждого элемента, поэтому операции с самим p очень ограничены.