Не эквивалентны [] [] и (* a) [] как параметры функции?

Прототип функции

void foo(int n, int a[][]);

дает ошибку об неполном типе, а

void foo(int n, int (*a)[]);  

компилирует. В соответствии с правилом распада int a[][] в этом случае эквивалентно int (*a)[], и поэтому int (*a)[] также должен давать ошибку об неполном типе, но GCC, похоже, его принимает. Есть что-то, чего я не вижу? Это может быть ошибка GCC, но я не нашел ничего связанного с ней.

Ответ 1

Нет, они не эквивалентны как параметры функции. Они не эквивалентны точно так же, как объявления параметров в foo и bar

struct S;
void foo(struct S* s); // OK
void bar(struct S a[]); // ERROR: incomplete type is not allowed

не эквивалентны.

C не допускает неполных типов в качестве элементов массива (см. 6.7.5.2/1: "[...] Тип элемента не должен быть неполным или функциональным. [...]" ), и это ограничение применяется к объявления параметров массива так же, как и для любых других объявлений массива. Даже несмотря на то, что параметры типа массива будут позднее неявно скорректированы на тип указателя, C просто не предоставляет специального обращения к объявлениям массива в списках параметров функций. Другими словами, объявления параметров массива проверяются на достоверность перед вышеупомянутой настройкой.

Твой int a[][] - это одно и то же: попытка объявить массив с элементами типа int [], который является неполным типом. Между тем, int (*a)[] совершенно легально - нет ничего необычного в указателях на неполные типы.

В качестве дополнительной заметки С++ "исправил" эту проблему, разрешив массивы неполного типа в объявлениях параметров. Однако исходный С++ по-прежнему запрещает параметры int a[][], параметры int (&a)[] и даже параметры int (*a)[]. Это предположительно было исправлено/разрешено позже на С++ 17 (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#393)

Ответ 2

Недопустимый тип разрешен в контекстах, где размер не требуется знать.

С помощью этого объявления:

int a[][]

Недопустимо даже в качестве параметра функции, потому что размер одного измерения массива необходим, чтобы знать, как выполнить арифметику указателя во втором измерении.

Это, однако, действительно:

int (*a)[];

Поскольку размер массива не нужно знать, чтобы использовать указатель на него.

В разделе 6.2.7 стандарт C приведен пример такого объявления:

5 ПРИМЕР Учитывая следующие два объявления области видимости файла:

int f(int (*)(), double (*)[3]);
int f(int (*)(char *), double (*)[]);

Результирующий составной тип для функции:

int f(int (*)(char *), double (*)[3]);

В этом примере показано объявление типа double (*)[3], которое совместимо с объявлением типа double (*)[]

Однако вы не можете использовать это как 2D-массив из-за недостающего размера. Вот несколько примеров для иллюстрации. Если вы попытаетесь сделать это:

void foo(int n, int (*a)[])
{
    int i,j;
    for (i=0;i<n;i++) {
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,a[i][j]);
        }
    }
}

Компилятор (как и ожидалось) сообщает вам следующее:

error: invalid use of array with unspecified bounds
         printf("a[%d][%d]=%d\n",i,j,a[i][j]);
         ^

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

#include <stdio.h>

void foo(int n, int (*a)[])
{
    int i,j;
    for (i=0;i<n;i++) {
        // dereference "a", type is int[], which decays to int *
        // now manually add "n" ints times the row
        int *b = *a + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }
}

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    foo(2,a);

    return 0;
}

Этот компилятор очищается со следующим выходом:

a[0][0]=4
a[0][1]=5
a[1][0]=6
a[1][1]=7

Даже вне функции синтаксис int (*)[] может использоваться:

#include <stdio.h>

int main()
{
    int a[2][2] = { {4,5},{6,7} };
    int i,j,n=2;
    int (*aa)[];
    // "a" decays from int[2][2] to int (*)[2], assigned to int (*)[]
    aa = a;
    for (i=0;i<n;i++) {
        int *b = *aa + (n*i);
        for (j=0;j<n;j++) {
            printf("a[%d][%d]=%d\n",i,j,b[j]);
        }
    }

    return 0;
}

Ответ 3

ИЗМЕНИТЬ

Прочитав все соответствующие части стандарта, C11 6.7.6.2 и 6.7.6.3, Я считаю, что это ошибка/несоответствие компилятора., по-видимому, сводится к тексту, который комитет прокрался в середине абзаца, касающегося разделителей массива. 6.7.6.2/1 акцент мой:

В дополнение к необязательным типам классификаторов и ключевому слову static, [ и] может ограничивать выражение или *. Если они ограничивают выражение (который определяет размер массива), выражение должно иметь целочисленный тип. Если выражение является постоянным выражением, оно должно имеют значение больше нуля. T тип элемента не должен быть неполный или тип функции. Необязательные типы классификаторов и ключевое слово static должно появляться только в объявлении функции параметр с типом массива, а затем только в самом удаленном массиве тип.

Теперь это, конечно, очень плохо написано, в основном говорится

"периферийная особенность, представляющая небольшой интерес, периферическая особенность интерес, периферическая особенность, представляющая небольшой интерес, ИЗ СИНИЙ ЗДЕСЬ ПРИНИМАЕТ НЕКОТОРЫЕ ТИПЫ ТИПА НЕКОТОРЫХ ТРАВМ, НЕ СВЯЗАННЫЕ С ОТДЕЛОМ ЭТОГО ПАРАГРАФА, периферийная функция, представляющая небольшой интерес, периферийная функция мало интересует,..."

Так легко понять, обмануть меня.

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

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


Только для конкретного случая void foo(int n, int a[][]); это объявление функции. Это не определение.

C11 6.7.6.3/12

Если декларатор функции не является частью определения этого функция, параметры могут иметь неполный тип

Итак, в первую очередь, параметрам разрешено иметь неполный тип в объявлении функции. Стандарт ясен. Вот почему код вроде этого компилируется просто отлично:

struct s; // incomplete type
void foo(int n, struct s a); // just fine, incomplete type is allowed in the declaration

Далее

C11 6.7.6.3/4

После настройки параметры в списке типов параметров в объявлении функции, который является частью определения этой функции, не должны иметь неполного типа.

После настройки очень важно здесь.

Значение, которое после настройки int a[][] - int (*a)[], параметр не должен иметь неполный тип. Это не так, это указатель на неполный тип, который всегда разрешен и отлично.

Компилятору не разрешается сначала оценивать int a[][] как неполный массив неполных массивов, а затем настроить его (если он обнаружил, что тип не был неполным). Это непосредственно нарушит 6.7.6.3/4.