Я только что заметил, что int arr2[(777, 100)]
является допустимым декларатором массива, а int arr1[777, 100]
— нет.
Более подробный пример компиляции:
#include <stdio.h>
void f(int i) {
printf("side effect %d\n", i);
}
int main(void) {
// int arr1[777, 100]; // illegal, it seems
int arr2[(777, 100)];
int arr3[(f(777), 100)];
arr2[10, 20] = 30;
arr3[f(40), 50] = 60;
return 0;
}
который отлично компилируется с GCC (но по какой-то причине не с MSVC).
Также обратите внимание, как приведенный выше код иллюстрирует, насколько хороши выражения с запятыми в контексте без декларатора.
Причина (почему скобки необходимы в деклараторах массива), по-видимому, заключается в том, что в стандарте C размер массива в квадратных скобках является выражением присваивания, а не выражением (проект C17, 6.7.6.2 ¶3 и A.2.1); последний является синтаксическим уровнем для операторов запятой (проект C17, A.2.1 (6.5.17)):
выражение:
выражение-присвоение
выражение
,
выражение-присваивание
Если расширить выражение-присвоение, то в конечном итоге доберешься до уровня первичного выражения (проект C17, A.2.1 (6.5.1)):
первичное выражение:
идентификатор
постоянный
строковый литерал
(
выражение)
общий выбор
Если стандарт так говорит, то так оно и есть, но: есть ли синтаксическая необходимость? Возможно, причина кроется в соображениях проектирования языка. В стандарте C перечислены следующие 4 формы деклараторов массива (проект C17, 6.7.6.2, пункт 3):
D [
список-квалификаторов типовopt выражение-присваиванияopt]
D [
список-квалификаторов типовopt выражение-присвоения]
D [
список-квалификаторов типовstatic
выражение-присваивания]
D [
список-квалификаторов типовopt]
static
).int arr[m, n]
, когда они действительно имеют в виду int arr[m][n]
.Кстати, если у кого-нибудь есть комментарии по поводу того, почему это не компилируется с MSVC, это также будет оценено по достоинству.
Кстати, если кто-нибудь знает, как получить разметку с интервалом в начале строки (для выдержек синтаксиса), который отличается от `␣␣`
(␣
: пробел), который я использую, дайте мне знать или не стесняйтесь редактировать мое сообщение напрямую.
Обычно я использую блоки кода, если мне нужно такое форматирование. Вы также можете использовать маркированный список.
Какой смысл int arr2[(777, 100)];
? Это эквивалентно int arr2[100];
И int arr3[(f(777), 100)];
эквивалентно f(777); int arr3[100];
.
@Barmar Моя отговорка в том, что это вопрос «языкового юриста» ;-) Хотя я не буду отвергать практически полезные примеры.
Вы смотрели грамматическую спецификацию VLA? Это, вероятно, объяснит, зачем вам нужны круглые скобки. Я подозреваю, что причина в том, чтобы люди не думали, что они могут использовать запятые для объявления и доступа к многомерным массивам.
Аналогично тому, почему вам нужно использовать круглые скобки, чтобы поместить запятую в аргументы функции — в противном случае возникает двусмысленность с запятой, разделяющей аргументы.
@Бармар Я (посмотрел). // Я упомянул об этом (обосновании) как о возможности, но есть ли на самом деле необходимость?
@LoverofStructure Я не думаю, что в этом есть необходимость. Разработчики языка иногда основывают свои решения на практических или эстетических соображениях.
@LoverofStructure Насколько я помню, (
после for
(и, возможно, после while
тоже) на самом деле не обязателен.
@pmor Круглые скобки являются частью синтаксиса циклов for
и while
(проект C17, 6.8.5, ¶1).
@LoverofStructure Перефразирование: насколько я помню, технически (с точки зрения автора компилятора) (
после for
(и, возможно, после while
тоже) на самом деле не является необходимым.
@pmor Ах, ладно, вы приводите пример элемента синтаксиса C, который, строго говоря, избыточен. Понятно.
@LoverofStructure Знаете ли вы, являются ли в sizeof ( type-name )
(как и в _Alignof ( type-name )
) ()
технически необходимыми (т. е., строго говоря, неизбыточными)?
@pmor Здесь есть ответ: stackoverflow.com/a/26702432/2057969 (На мгновение я подумал, что можно утверждать, что sizeof int * + 0
следует анализировать как (sizeof int) * (+ 0)
, но на самом деле я думаю, что пример работает, потому что *
внутри Обозначение типа фактически не подпадает под действие стандартных правил приоритета операторов. Обратите внимание, что я не проверял, была бы грамматика, приведенная в стандарте, буквально двусмысленной, если бы sizeof typename
был разрешен — как вы знаете, правила приоритета операторов являются только неявными. стандарт, потому что они следуют из его грамматики.)
В спецификации грамматики объявления массива длина представляет собой выражение присваивания:
Если в декларации «Т Д1» Д1 имеет одну из форм:
D [список-квалификаторов-типовoptвыражение-назначенияopt ] последовательность-спецификатора-атрибутаopt
D [ статический список-списк-квалификаторовopt выражение-назначения ] последовательность-спецификатора-атрибутаopt
D [ статическое выражение-назначения-списка-типа ] последовательность-спецификатора-атрибутаopt
D [ список-квалификаторов-типовopt * ] последовательность-спецификатор-атрибутаopt
и тип, указанный для ident в объявлении «TD», — это «список типов производного-декларатора T», тогда тип, указанный для ident, - это "массив списка производных-деклараторов-типов T"
А в спецификации грамматики выражений выражение-присвоение не включает выражение, в котором можно использовать оператор запятая. Выражение-присваивания находится чуть ниже выражения в иерархии грамматики выражений.
выражение:
выражение-присвоение
выражение, выражение-присваивания
Поэтому вам придется заключить выражение с запятой в круглые скобки, чтобы сделать его первичным выражением, которое находится в самом основании иерархии выражений.
Вопрос касается того, как грамматика требует скобок для использования оператора запятой в этом контексте. Вопрос заключается в том, почему грамматика такая («Есть ли синтаксическая необходимость? Возможно, есть причина, основанная на соображениях языкового дизайна»). Этот пост не отвечает на этот вопрос.
Если нет обоснования или если один из членов комитета не желает высказаться, лучшее, что мы можем сделать, это строить предположения. Я сделал это в комментарии, я не хочу говорить об этом в ответе.
Как упоминалось в вопросе, отказ от принятия нескольких выражений, разделенных запятыми, позволяет избежать потенциальных ошибок со стороны людей, привыкших к другим языкам программирования, которые используют этот синтаксис для нескольких измерений массива.
Указание в грамматике выражения-присваивания, а не выражения, исключает только оператор запятой. Помимо уже предполагаемой причины, единственный эффект, который я вижу, — это использование макросов. int a[3, 4]
будет анализироваться как два аргумента функционального макроса1, тогда как int a[(3, 4)]
будет одним. Но без какого-либо примера использования макроса, связанного с этим, я не вижу в этом причины.
1 Например, Foo(int a[3, 4])
будет анализироваться как вызов Foo
с одним аргументом int a[3
и другим 4]
.
MSVC не поддерживает массивы переменной длины, поэтому размер массива должен быть литералом, а не выражением.