Почему 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}?

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

const char str[] = "Str";

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


person esgaldir    schedule 19.02.2018    source источник
comment
Что блокирует компилятор, так это соблюдение стандарта (для C или C++ это разные стандарты, выберите один). Что не позволяет стандарту разрешить это, так это то, что никто не написал предложение по стандарту для его реализации, которое впоследствии было принято.   -  person Useless    schedule 19.02.2018
comment
^ - Это. Что многое говорит вам о том, насколько истинная потребность в этой функции может возникнуть на практике.   -  person StoryTeller - Unslander Monica    schedule 19.02.2018
comment
Борьба за то, должны ли инициализаторы разного размера быть ошибкой или размерность должна быть больше, будет длиться десятилетиями.   -  person molbdnilo    schedule 19.02.2018
comment
Что мешает компилятору посмотреть... --› Мало что мешает. Почему... невозможно --› C не хватает возможностей: двоичных констант, перегрузки функций. Требуется работа над зарождающейся поддержкой Unicode, _Generic. [][] = {{…}, {…}} не является приоритетом для изменения Спецификации - хоть и интересно.   -  person chux - Reinstate Monica    schedule 19.02.2018


Ответы (5)


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

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

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
comment
Кстати, C99 позволяет что-то вроде: int *q[5] = {(int[]){1,2,3,-1}, (int[]){1,2,-1}, (int[]){1,2,3,4,5,6,7,-1}};. Синтаксис немного неуклюж, код должен был бы использовать (int*)[], а не двумерный массив, и не было бы хорошего способа узнать внутренние измерения, если бы это не подразумевалось данными [например, путем включения часовых в конце каждой строки], но этот подход может быть более эффективным, чем попытка использовать двумерный массив, если строки будут иметь разное количество инициализаторов. - person supercat; 19.02.2018

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

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

person Armen Tsirunyan    schedule 19.02.2018

Чтобы кратко расширить комментарий:

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

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

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

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

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

person Useless    schedule 19.02.2018

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

person haccks    schedule 19.02.2018

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

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 были довольно ограничены, и описанное выше поведение было задано ожидаемым задолго до появления современных компиляторов.

person JeremyP    schedule 19.02.2018
comment
Также существует проблема передачи массива в функцию. Если компилятор не передаст дополнительную (скрытую) информацию, функция не сможет узнать размеры. И передача этой информации приведет к поломке массива — это просто парадигма указателя. - person jamesqf; 19.02.2018
comment
@jamesqf Я собирался написать что-то о передаче параметров, но забыл. В объявлении функции вам нужно указать размер последнего индекса в 2D-массиве. - person JeremyP; 20.02.2018