Требование, чтобы компилятор выводил внутренние измерения из инициализаторов, потребовало бы, чтобы компилятор работал задним числом, чего стандарт избегает.
Стандарт позволяет инициализируемым объектам ссылаться на самих себя. Например:
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;
, но они заполняются компоновщиком.)
Кроме того, рассмотрим такое определение, как:
char x[][] =
{
{ 0, 1 },
{ 10, 11 },
{ 20, 21, 22 }
};
Когда компилятор читает первые две строки начальных значений, он может захотеть подготовить копию массива в памяти. Итак, когда он читает первую строку, он будет хранить два значения. Затем он видит конец линии, поэтому на данный момент он может предположить, что внутреннее измерение равно 2, образуя char x[][2]
. Когда он видит вторую строку, он выделяет больше памяти (как в случае с realloc
) и продолжает, сохраняя следующие два значения, 10 и 11, на соответствующих местах.
Когда он читает третью строку и видит 22
, он понимает, что внутреннее измерение равно как минимум трем. Теперь компилятор не может просто выделить больше памяти. Он должен изменить расположение 10 и 11 в памяти относительно 0 и 1, потому что между ними есть новый элемент; x[0][2]
теперь существует и имеет значение 0 (пока что). Таким образом, требуя, чтобы компилятор выводил внутренние измерения, а также допускал различное количество инициализаторов в каждом подмассиве (и вывод внутреннего измерения на основе максимального количества инициализаторов, видимых во всем списке), может обременить компилятор большим объемом памяти.
person
Eric Postpischil
schedule
19.02.2018
[][] = {{…}, {…}}
не является приоритетом для изменения Спецификации - хоть и интересно. - person chux - Reinstate Monica   schedule 19.02.2018