Подтвердить что ты не робот

Почему синтаксис "int (*) [*]" необходим в C?

Просто я искал что-то в ISO/IEC9899. Когда я наткнулся на это:

6.7.6 Имена типов

[...]

Семантика

2 В нескольких контекстах необходимо указать тип. Это выполняется с использованием типа имя, которое является синтаксически объявлением для функции или объекта этого типа, который опускает идентификатор .128) 3 ПРИМЕР Конструкции

(a) int
(b) int *
(c) int *[3]
(d) int (*)[3]
(e) int (*)[*]
(f) int *()
(g) int (*)(void)
(h) int (*const [])(unsigned int, ...)

name соответственно типы (a) int, (b) указатель на int, (c) массив из трех указателей на int, (d) указатель на массив из трех ints, (e) указатель на массив переменной длины неопределенного числа ints, (f) function без спецификации параметра, возвращающей указатель на int, (g) указатель на функцию без параметров возвращающий int и (h) массив неопределенного числа постоянных указателей на функции, каждый с одним параметр, который имеет тип unsigned int и неопределенное количество других параметров, возвращая внутр.

Что меня больше всего смутило:

(e) указатель на массив переменной длины неопределенного числа ints

Другие, которые я могу понять более или менее. Но каково использование указателя на VLA неуказанного числа "ints"?

И есть ли необходимость в компиляторе для поддержки синтаксиса

int foo[*];

?

EDIT для уточнения

Этот вопрос primaly нацелен на "Требуется ли вообще поддерживать это для компилятора?". В то время как эта запись ANSI-C грамматики - объявления массива, подобные [*] et alii, явно улучшили мои знания. По-прежнему нет ответа: почему компилятор должен знать, является ли параметр прототипа просто адресом, который содержит неизвестный размер. как с просто выполнением int foo[], или это будет неопределенный размер?

Итак, действительно ли это необходимо для поддержки? И если не так, почему стандарт даже реализует эту семантику?

4b9b3361

Ответ 1

Если я передаю массив с более чем одним измерением функции, и если параметры функции, используемые для выражения числа элементов в заданной размерности массива, приходят после самого параметра массива, синтаксис [*] может быть используемый. В случае массива с более чем двумя измерениями, и если параметр массива, опять же, предшествует параметрам подсчета элементов, этот синтаксис должен использоваться, поскольку разложение массива происходит только один раз. В конце концов, вы не можете использовать int (*)[][] или int [][][], потому что стандарт требует, чтобы в int [A][B] и int [A][B][C][D] только A мог быть опущен из-за разложения массива на указатель. Если вы используете нотацию указателя в параметре функции, вам разрешено использовать int (*)[], но это имеет для меня очень мало смысла, тем более, что:

  • sizeof ptr[0] и sizeof *ptr являются незаконными - как должен компилятор определить размер массива с неопределенным количеством элементов? Вместо этого вы должны найти его во время выполнения, используя N * sizeof **ptr или sizeof(int (*)[N]). Это также означает, что любые арифметические операции на ptr, такие как использование ++ptr, являются незаконными, поскольку они полагаются на информацию о размере, которая не может быть рассчитана. Для этого можно использовать приведение типов, но проще использовать локальную переменную с соответствующей информацией о типе. Опять же, почему бы просто не использовать синтаксис [*] и включить информацию о правильном типе с самого начала?
  • sizeof ptr[0][0] является незаконным, но sizeof (*ptr)[0] не является - индексирование массива все еще выполняется даже при простом получении информации, подобной размеру, поэтому это похоже на запись sizeof (*(ptr + 0))[0], что является незаконным, поскольку вы не можете применять арифметические операции к неполный тип, как указано ранее.
  • Кто-то, кто никогда не сталкивался с этой проблемой раньше, может подумать, что [] может быть заменен на *, уступая int ** вместо int (*)[], что неверно, потому что этот подматрица не затухает. Распад массива происходит только один раз.

Я заметил, что синтаксис [*] не нужен, если параметры, используемые как подсчет элементов, были первыми, что верно, но когда последний раз кто-нибудь видел одно из следующих?

void foo (int a, int b, int c, int arr[a][b][c]);

void bar (int a, int b, int c, int arr[][b][c]);

void baz (int a, int b, int c, int (*arr)[b][c]);

Итак, чтобы ответить на ваш вопрос:

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

и

  • параметры, обозначающие количество элементов, перечисляются после самого параметра массива,

может потребоваться синтаксис [*]. Я действительно поощряю использование [*], так как [] возникает с проблемами, когда требуется информация о размере.

Ответ 2

Почему компилятор должен знать, является ли параметр прототипа просто адресом, который содержит неизвестный размер. как с просто выполнением int foo [], или это будет неопределенный размер?

Компилятору не нужно ничего "знать", это инструмент.

Разница между int (*)[*] и int[] примерно такая же, как между int (*)[5] и int[]. Если вы согласны с тем, что последняя пара не является взаимозаменяемой, то первая тоже не является.

В пред-C99 способ указать массив неизвестного числа элементов T - T[]. Это неполный тип, что означает, что вы не можете иметь массив T[]. Нет T[][]. Внутри декларатора функции T[] означает то же, что и T*. OTOH T[*] - массив переменной длины, который отличается от массива неизвестного количества элементов. Вы можете иметь массив массивов переменного размера, т.е. Есть T[*][*]. Синтаксис, о котором вы просите, необходим для поддержки этого типа массива с переменным размером. К счастью, вы не спрашиваете, почему нам нужны разные типы, потому что ответ был бы очень длинным, но здесь я на него наносил удар.

Назначение типов в два раза. Во-первых, типы необходимы для генерации объектного кода (такие вещи, как a++, обычно генерируют разные объектные коды, в зависимости от типа a). Во-вторых, типы, необходимые для проверки типов (такие вещи, как a++, могут быть разрешены или нет в зависимости от типа a).

Типы [*] допускаются только в деклараторах функций, которые не являются частями определений функций. Итак, генерация кода и здесь не актуальна. Это оставляет нам проверку типов. Действительно,

int foo(int, int (*)[*]);
int bar(int, int (*)[5]);

int main ()
{
    int a;
    int aa[5];
    int aaa[5][5];

    foo(1, &a);    // incorrect, `&a` is `int*`, `int*` and `int (*)[*]` are different
    bar(1, &a);    // incorrect, `&a` is `int*`, `int*` and `int (*)[5]` are different
    foo(5, aa);    // incorrect, `aa` is `int*` (!), `int*` and `int (*)[*]` are different
    bar(5, aa);    // incorrect, `aa` is `int*` (!), `int*` and `int (*)[5]` are different
    foo(5, &aa);   // correct
    bar(5, &aa);   // correct
    foo(5, aaa);   // correct
    bar(5, aaa);   // correct
}

Если мы согласны с тем, какие вызовы в bar являются правильными, а какие нет, мы должны также согласиться на вызовы foo.

Остается только вопрос: почему int foo(int m, int (*)[m]); недостаточно для этой цели? Вероятно, это так, но язык C не заставляет программиста называть формальные параметры в деклараторах функций, где имена параметров не нужны. [*] допускает эту небольшую свободу в случае VLA.

Ответ 3

Я собираюсь ответить на ваш вопрос строго по просьбе:

Этот вопрос primaly нацелен на "Требуется ли вообще поддерживать это для компилятора?"

Для компилятора C99 да: это часть стандарта, поэтому компилятор, совместимый с C99, должен его поддерживать. Вопрос о том, для чего полезен int foo[*];, довольно ортогонален вопросу о том, нужно ли его поддерживать. Все компиляторы, утверждающие, что соответствуют C99, которые я тестировал, поддерживали его (но я не уверен, для чего это полезно).


Для компилятора C11, хорошие новости! Массивы переменной длины были сделаны "условной функцией". Вы можете реализовать C11-совместимый без массивов переменной длины, если он определяет __STDC_NO_VLA__:

6.10.8.3 Макросы условных функций

...

__STDC_NO_VLA__ Целочисленная константа 1, предназначенная для указания, что реализация не поддерживает массивы переменной длины или измененные типы.

Ответ 4

In C99 it is possible to declare arrays using variable dimensions, providing the variable has a ( positive integer ) value at the time the declaration is made. It turns out that this carries over to the declaration of arrays in function parameters as well, which can be particularly useful for multi-dimensional arrays.
For example, in the prototype:

int arrayFunction( int nRows, int nCols, double x[ nRows ], 
                   double y[ nRows ][ nCols ] );

the variable dimension on x is informative to the human but not necessary for the computer, since we could have declared it as x[ ]. However the nCols dimension on y is very useful, because otherwise the function would have to be written for arrays with pre-determined row sizes, and now we can write a function that will work for arrays with any row length.

Для двух типов массивов, которые должны быть совместимы, оба должны иметь совместимые типы элементов, и если оба спецификатора размера присутствуют и являются целыми константными выражениями, то оба размера должны иметь одинаковое значение. VLA всегда совместим с другим типом массива, если оба они имеют один и тот же тип элемента. Если два типа массива используются в контексте, который требует их совместимости, это поведение undefined, если размеры измерения не равны во время выполнения

Ответ 5

Может быть полезно, если вы хотите работать с массивами "зубчатых", когда размер строк этой матрицы, неизвестный во время компиляции, будет инициализирован во время выполнения и останется неизменным во время целого время исполнения. Но чтобы убедиться, что вы останетесь в границах для каждой строки, вам нужно будет хранить фактические размеры этого массива как-то отдельно для каждой строки, если хотите, чтобы она была "зубчатой", потому что оператор sizeof не будет работать должным образом для инициализированных массивов времени выполнения ( он вернет размер указателя в лучшем случае, так как это оператор времени компиляции).