Почему выражение с запятой, используемое в качестве размера массива, должно быть заключено в круглые скобки, если оно является частью декларатора массива?

Я только что заметил, что 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, это также будет оценено по достоинству.

MSVC не поддерживает массивы переменной длины, поэтому размер массива должен быть литералом, а не выражением.

Barmar 06.04.2024 05:56

Кстати, если кто-нибудь знает, как получить разметку с интервалом в начале строки (для выдержек синтаксиса), который отличается от `␣␣` (: пробел), который я использую, дайте мне знать или не стесняйтесь редактировать мое сообщение напрямую.

Lover of Structure 06.04.2024 05:56

Обычно я использую блоки кода, если мне нужно такое форматирование. Вы также можете использовать маркированный список.

Barmar 06.04.2024 05:57

Какой смысл int arr2[(777, 100)];? Это эквивалентно int arr2[100];

Barmar 06.04.2024 05:58

И int arr3[(f(777), 100)]; эквивалентно f(777); int arr3[100];.

Barmar 06.04.2024 05:59

@Barmar Моя отговорка в том, что это вопрос «языкового юриста» ;-) Хотя я не буду отвергать практически полезные примеры.

Lover of Structure 06.04.2024 06:00

Вы смотрели грамматическую спецификацию VLA? Это, вероятно, объяснит, зачем вам нужны круглые скобки. Я подозреваю, что причина в том, чтобы люди не думали, что они могут использовать запятые для объявления и доступа к многомерным массивам.

Barmar 06.04.2024 06:01

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

Barmar 06.04.2024 06:02

@Бармар Я (посмотрел). // Я упомянул об этом (обосновании) как о возможности, но есть ли на самом деле необходимость?

Lover of Structure 06.04.2024 06:04

@LoverofStructure Я не думаю, что в этом есть необходимость. Разработчики языка иногда основывают свои решения на практических или эстетических соображениях.

Barmar 08.04.2024 17:24

@LoverofStructure Насколько я помню, ( после for (и, возможно, после while тоже) на самом деле не обязателен.

pmor 07.05.2024 16:29

@pmor Круглые скобки являются частью синтаксиса циклов for и while (проект C17, 6.8.5, ¶1).

Lover of Structure 07.05.2024 16:57

@LoverofStructure Перефразирование: насколько я помню, технически (с точки зрения автора компилятора) ( после for (и, возможно, после while тоже) на самом деле не является необходимым.

pmor 07.05.2024 21:00

@pmor Ах, ладно, вы приводите пример элемента синтаксиса C, который, строго говоря, избыточен. Понятно.

Lover of Structure 08.05.2024 00:54

@LoverofStructure Знаете ли вы, являются ли в sizeof ( type-name ) (как и в _Alignof ( type-name )) () технически необходимыми (т. е., строго говоря, неизбыточными)?

pmor 08.05.2024 12:58

@pmor Здесь есть ответ: stackoverflow.com/a/26702432/2057969 (На мгновение я подумал, что можно утверждать, что sizeof int * + 0 следует анализировать как (sizeof int) * (+ 0), но на самом деле я думаю, что пример работает, потому что * внутри Обозначение типа фактически не подпадает под действие стандартных правил приоритета операторов. Обратите внимание, что я не проверял, была бы грамматика, приведенная в стандарте, буквально двусмысленной, если бы sizeof typename был разрешен — как вы знаете, правила приоритета операторов являются только неявными. стандарт, потому что они следуют из его грамматики.)

Lover of Structure 08.05.2024 14:27
Структурированный массив Numpy
Структурированный массив Numpy
Однако в реальных проектах я чаще всего имею дело со списками, состоящими из нескольких типов данных. Как мы можем использовать массивы numpy, чтобы...
T - 1Bits: Генерация последовательного массива
T - 1Bits: Генерация последовательного массива
По мере того, как мы пишем все больше кода, мы привыкаем к определенным способам действий. То тут, то там мы находим код, который заставляет нас...
Что такое деструктуризация массива в JavaScript?
Что такое деструктуризация массива в JavaScript?
Деструктуризация позволяет распаковывать значения из массивов и добавлять их в отдельные переменные.
1
16
141
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

В спецификации грамматики объявления массива длина представляет собой выражение присваивания:

Если в декларации «Т Д1» Д1 имеет одну из форм:
D [список-квалификаторов-типовoptвыражение-назначенияopt ] последовательность-спецификатора-атрибутаopt
D [ статический список-списк-квалификаторовopt выражение-назначения ] последовательность-спецификатора-атрибутаopt
D [ статическое выражение-назначения-списка-типа ] последовательность-спецификатора-атрибутаopt
D [ список-квалификаторов-типовopt * ] последовательность-спецификатор-атрибутаopt
и тип, указанный для ident в объявлении «TD», — это «список типов производного-декларатора T», тогда тип, указанный для ident, - это "массив списка производных-деклараторов-типов T"

А в спецификации грамматики выражений выражение-присвоение не включает выражение, в котором можно использовать оператор запятая. Выражение-присваивания находится чуть ниже выражения в иерархии грамматики выражений.

выражение:
выражение-присвоение
выражение, выражение-присваивания

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

Вопрос касается того, как грамматика требует скобок для использования оператора запятой в этом контексте. Вопрос заключается в том, почему грамматика такая («Есть ли синтаксическая необходимость? Возможно, есть причина, основанная на соображениях языкового дизайна»). Этот пост не отвечает на этот вопрос.

Eric Postpischil 06.04.2024 12:59

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

Barmar 08.04.2024 17:22
Ответ принят как подходящий

Как упоминалось в вопросе, отказ от принятия нескольких выражений, разделенных запятыми, позволяет избежать потенциальных ошибок со стороны людей, привыкших к другим языкам программирования, которые используют этот синтаксис для нескольких измерений массива.

Указание в грамматике выражения-присваивания, а не выражения, исключает только оператор запятой. Помимо уже предполагаемой причины, единственный эффект, который я вижу, — это использование макросов. int a[3, 4] будет анализироваться как два аргумента функционального макроса1, тогда как int a[(3, 4)] будет одним. Но без какого-либо примера использования макроса, связанного с этим, я не вижу в этом причины.

Сноска

1 Например, Foo(int a[3, 4]) будет анализироваться как вызов Foo с одним аргументом int a[3 и другим 4].

Другие вопросы по теме