Почему C не определяет минимальный размер массива?

Стандарт C определяет множество нижних/верхних ограничений (ограничения перевода<). /strong>) и накладывает реализацию, которая должна удовлетворять каждому переводу. Почему нет такого минимального предела, определенного для размера массива? Следующая программа скомпилируется нормально и, вероятно, выдаст ошибку/segfault во время выполнения и вызовет неопределенное поведение.

int main()
{
   int a[99999999];
   int i;

   for(i=0;i<99999999;i++)
   a[i]=i;

   return 0;
}

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

Давайте забудем о неопределенных случаях, подобных приведенным выше. Рассмотрим следующее:

int main()
{
   int a[10];
   int i;

   for(i=0;i<10;i++)
   a[i]=i;

   return 0;
}

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

Хотя маловероятно, что выделение для такого небольшого массива не удастся на каких-либо современных системах. Но стандарт C не определяет каких-либо требований, и компиляторы не сообщают (по крайней мере, GCC) об ошибках выделения памяти. Возможна только ошибка времени выполнения/неопределенное поведение. Сложность заключается в том, что никто не может сказать, приведет ли массив произвольного размера к неопределенному поведению из-за сбоя выделения.

Обратите внимание, что я знаю, что могу использовать динамические массивы (через malloc и др.) для этой цели и иметь лучший контроль над ошибками распределения. Меня больше интересует, почему для локальных массивов не определено такое ограничение. Кроме того, глобальные массивы будут храниться в статической памяти, что увеличит размер исполняемого файла, с которым могут справиться компиляторы.


person P.P    schedule 04.02.2013    source источник
comment
Трудная часть заключается в том, что никто не может сказать, будет ли массив произвольного размера вызывать неопределенное поведение из-за сбоя выделения. - Как вы предлагаете это решать? Какой минимум вы бы использовали? Имейте в виду разнообразие платформ, на которых работает C, от крошечных микроконтроллеров с их крошечными стеками до 64-битных архитектур с очень большими стеками. Кроме того, рекурсия может сильно повлиять на доступность стека. Если вы программируете на C, вы, как правило, знаете свое оборудование и, следовательно, доступность стека.   -  person Pete    schedule 05.02.2013
comment
@KingsIndian Я думаю, что решение состоит в том, чтобы избегать использования стека в ситуациях, которые могут оказать на него неожиданное давление, и знать пределы требований к выполнению. Это похоже на чрезмерное количество рекурсий: в одних средах UB будет создаваться гораздо раньше, чем в других.   -  person    schedule 05.02.2013
comment
@KingsIndian Это хорошо написано. Я пытался придумать название получше, но ваш последний абзац устраняет такой фокус :) Надеюсь, он останется открытым, так как я думаю, что есть хорошая информация и ответы.   -  person    schedule 05.02.2013


Ответы (3)


МИНИМАЛЬНЫЙ предел — это массив из 1 элемента. Зачем тебе "лимит" на это? Конечно, если вы вызываете функцию рекурсивно навсегда, массив из 1 может не поместиться в стеке, или вызов, вызывающий следующую функцию, может не поместиться в стеке — единственный способ решить эту проблему — узнать размер стека в компиляторе - но на этом этапе компилятор на самом деле не знает, насколько велик стек - не говоря уже о проблемах чрезвычайно сложных иерархий вызовов, когда несколько разных функций вызывают одну и ту же функцию, возможно, с рекурсией и/или несколько слоев довольно больших потребителей стека - как вы определяете размер стека для этого - наихудший возможный случай может никогда не встретиться, потому что другие вещи диктуют, что этого не происходит - например, наихудший случай в одной функции только когда входной файл пуст, но худший случай в другой функции - это когда в одном файле хранится много данных. Таких вариаций очень много. Это слишком ненадежно, чтобы определить, поэтому рано или поздно это станет просто догадкой или множеством ложных срабатываний.

Рассмотрим программу с тысячами функций, каждая из которых вызывает одну и ту же функцию ведения журнала, которой требуется 200-байтовый массив в стеке для временного хранения вывода журнала. Он вызывается практически из каждой функции, начиная с main.

МАКСИМУМ для локальной переменной зависит от размера стека, который, как я сказал выше, не является чем-то, что компилятор знает при компиляции вашего кода [компоновщик МОЖЕТ знать, но это позже]. Для глобальных массивов и массивов, выделенных в куче, предел равен «сколько памяти может получить ваш процесс», поэтому здесь нет верхнего предела.

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

int main () { пока (1); }

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

Вы также можете поместить в стек большие массивы. И вполне может быть, что компоновщику дается несколько гигабайт стека, и в этом случае все будет в порядке - или стек 200К, и у вас не может быть массива целых 50000...

person Mats Petersson    schedule 04.02.2013
comment
127 вложенных уровней могут вообще не требовать никакой памяти (кроме пространства кода). Если вы делаете много вложенных операторов if/while/switch, они не используют пространство стека, если только в них нет локальных переменных. Очевидно, что много вложенных if и while означает, что некоторые данные должны использоваться, но не обязательно все. Опять же, это ограничение гарантирует, что если вы пишете код, который имеет (не более) 127 уровней, он может быть скомпилирован любым (совместимым) компилятором. Код может не поместиться в 8 КБ ОЗУ, которые есть в системе, но компилятор ничего не может с этим поделать... - person Mats Petersson; 05.02.2013

Потому что C, язык, не должен накладывать ограничения на доступный размер стека. C работает во многих (многих) различных средах. Как он мог получить разумное число? Черт, продолжительность автоматического хранения != стек, стек - это деталь реализации. C, язык, ничего не говорит о "стеке".

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

Должны ли мы пересмотреть стандарт в таком случае? Нам пришлось бы, если бы C, язык, указывал такие детали реализации.

person Ed S.    schedule 04.02.2013

Вы уже ответили на свой вопрос; это связано с ограничением стека.* Даже это может не сработать:

void foo(void) {
    int a;

    ...
}

если ... на самом деле является рекурсивным вызовом foo.

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


* Да, я знаю, что стандарт(ы) C не говорит о стеках. Но это неявная модель, в том смысле, что стандарт на самом деле был формализацией реализаций, существовавших в то время.

person Oliver Charlesworth    schedule 04.02.2013
comment
но это не то, что он спрашивает - person Keith Nicholas; 05.02.2013
comment
Кроме того, стек никогда не предназначался для хранения больших вещей. Он был предназначен для относительно небольших вещей, тогда как большие вещи предназначались для кучи. Поэтому, если вы пытаетесь разместить в стеке гигантские массивы, вы просто используете не тот инструмент для этой работы. - person Pete; 05.02.2013
comment
@KeithNicholas: Тогда о чем он спрашивает? - person Oliver Charlesworth; 05.02.2013
comment
@OliCharlesworth: он спрашивает, почему стандарт не требует реализации для поддержки определенного минимального размера массива, чтобы соответствовать требованиям. - person Sander De Dycker; 05.02.2013
comment
@SanderDeDycker: я предположил, что это сразу следует из моего ответа! А именно, потребуется, чтобы реализация предлагала бесконечный стек, чтобы оставаться совместимым. - person Oliver Charlesworth; 05.02.2013
comment
@KingsIndian: я думаю, что это действительно ограничение времени компиляции; то есть, сколько вложенности должен компилятор иметь возможность отслеживать, прежде чем он сломается. - person Oliver Charlesworth; 05.02.2013
comment
@OliCharlesworth: это само по себе действительно ответ (если не ответ). Упоминание этого явно (как вы сейчас сделали) было необходимо, чтобы недвусмысленно донести суть. - person Sander De Dycker; 05.02.2013
comment
А как насчет того, кто захочет реализовать компилятор для процессора 6502, у которого всего 256 байт стека? [Таким образом, аргументы, вероятно, должны были бы находиться в отдельном стеке, управляемом программным обеспечением, поскольку хранение только регистров и адресов возврата вскоре заняло бы 256 байтов стека]. - person Mats Petersson; 05.02.2013
comment
@MatsPetersson: Кому адресован ваш вопрос? - person Oliver Charlesworth; 05.02.2013
comment
В KingsIndian в первую очередь - дело в том, что C (и чуть в меньшей степени C++) дает основу для реализации компилятора практически для любого целевого процессора. Таким образом, он не регулирует, например, сколько уровней стека будет поддерживать код — я полагаю, что он также не устанавливает минимума или максимума для глобальных переменных или размера кода — но, конечно, если кто-то пишет 200 КБ кода, и попробуйте запустить его на Z80 с 64 КБ адресного пространства, это не сработает, независимо от того, имеет ли он менее 127 уровней вложенности блоков или нет. - person Mats Petersson; 05.02.2013
comment
@KingsIndian: посмотрите на это так: для того, чтобы удовлетворить ваши потребности, стандарт должен требовать, чтобы - независимо от того, сколько памяти используется в настоящее время - мог быть создан еще один массив размера x. Все, что меньше этого, было бы бесполезно, потому что всегда есть шанс, что вы окажетесь в ситуации, когда для массива нет места, и, следовательно, вызывается неопределенное поведение, а это именно то, чего вы хотите избежать. Такое требование было бы невозможно выполнить. - person Sander De Dycker; 05.02.2013
comment
@SanderDeDycker Точно. Что, если вы запускаете функцию, вложенную в 2000 уровней, и все родительские функции имеют в себе массив из 100 элементов? Это заняло бы не менее 208000 байт стека. Хотя это может показаться возмутительным, у кого-то может быть глупо написанная рекурсивная функция... - person Kaslai; 05.02.2013