Почему char [] [] = {{...}, {...}} невозможно, если явно задан многомерный массив?

Я просмотрел эту статью. Я понимаю, что правила объяснены, но мне интересно, что именно блокирует компилятор от принятия следующего синтаксиса при определении постоянного многомерного массива и непосредственно инициализации его известными значениями данного типа:

const int multi_arr1[][] = {{1,2,3}, {1,2,3}}; // why not?
const int multi_arr2[][3] = {{1,2,3}, {1,2,3}}; // OK

error: declaration of 'multi_arr1' as multidimensional array must have bounds
       for all dimensions except the first

Что мешает компилятору смотреть вправо и понимает, что мы имеем дело с 3 элементами для каждого "подмассива" или, возможно, возвращаем ошибку только для случаев, когда программист передает, например, другое количество элементов для каждого подмассива, например {1,2,3}, {1,2,3,4}?

Например, при работе с массивом 1D char компилятор может посмотреть строку в правой части = и это действительно:

const char str[] = "Str";

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

Ответ 1

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

Стандарт позволяет инициализировать объекты, чтобы ссылаться на них. Например:

struct foo { struct foo *next; int value; } head = { &head, 0 };

Это определяет узел связанного списка, который указывает на него изначально. (Предположительно, позже будет добавлено больше узлов.) Это справедливо, потому что C 2011 [N1570] 6.2.1 7 говорит, что head идентификатора "имеет область видимости, которая начинается сразу после завершения ее декларатора". Декларатор является частью грамматики декларации, которая включает имя идентификатора вместе с массивом, функцией и/или частью указателя декларации (например, f(int, float) и *a[3] являются деклараторами, в объявлениях, таких как float f(int, float) или int *a[3]).

Из-за 6.2.1 7 программист мог написать это определение:

void *p[][1] = { { p[1] }, { p[0] } };

Рассмотрим инициализатор p[1]. Это массив, поэтому он автоматически преобразуется в указатель на его первый элемент, p[1][0]. Компилятор знает этот адрес, потому что он знает, что p[i] представляет собой массив из 1 void * (для любого значения i). Если компилятор не знал, насколько большой p[i], он не смог вычислить этот адрес. Итак, если стандарт C позволил нам написать:

void *p[][] = { { p[1] }, { p[0] } };

то компилятор должен продолжить сканирование через p[1] чтобы он мог подсчитать количество инициализаторов, заданных для второго измерения (только в этом случае, но мы должны сканировать хотя бы на } чтобы увидеть это, и это могло бы еще много), затем вернитесь и вычислите значение p[1].

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

(На самом деле, я думаю, что стандарту может не потребоваться, чтобы компилятор делал больше, чем конечное количество ожиданий, возможно, всего несколько символов во время токенизации и один токен при разборе грамматики, но я не уверен. имеют значения, не известные до времени ссылки, такие как void (*p)(void) = &SomeFunction; но они заполняются компоновщиком.)

Ответ 2

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

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

Ответ 3

Кратко развернуть комментарий:

Что "блокирует" компилятор - это соответствие стандарту (для C или C++, они разные стандарты, выберите один).

Что "блокирует" стандарт, позволяя это, никто не написал стандартное предложение для его реализации, которое впоследствии было принято.

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

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

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

Ответ 4

Правило заключается в том, что компилятор определяет только первое измерение массива с помощью данного списка инициализаторов. Он ожидает, что второе измерение будет указано явно. Период.

Ответ 5

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

int a[3];

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

Двумерный массив можно рассматривать как одномерный массив массивов. например

int b[2][3];

является двумерным массивом int но он также является одномерным массивом массивов int. т.е. b[x] относится к массиву из трех ints.

Даже с массивами массивов все еще применяется правило, согласно которому компилятор должен знать размер каждого элемента, что означает, что в массиве массивов второй массив должен иметь фиксированный размер. Если это не так, компилятор не смог вычислить адрес при индексировании, т.е. b[x] было бы невозможно вычислить. Следовательно, причина, по которой multi_arr2 в вашем примере в порядке, но multi_arr1 нет.

Что мешает компилятору искать вправо и утверждать, что мы имеем 3 элемента для каждого "подмассива" или, возможно, возвращаем ошибку только для случаев, когда программатор передает, например, различное количество элементов для каждого подмассива, например {1,2,3}, {1, 2,3,4}

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