Подтвердить что ты не робот

Почему компилятору C/С++ нужно знать размер массива во время компиляции?

Я знаю, что стандарты C, предшествующие C99 (а также С++), говорят, что размер массива в стеке должен быть известен во время компиляции. Но почему? Массив на стеке распределяется во время выполнения. Так почему размер имеет значение во время компиляции? Надеюсь, кто-то объяснит мне, что компилятор будет делать с размером во время компиляции. Благодарю.

Пример такого массива:

void func()
{
    /*Here "array" is a local variable on stack, its space is allocated
     *at run-time. Why does the compiler need know its size at compile-time?
     */
   int array[10]; 
}
4b9b3361

Ответ 1

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

Локальные переменные, как правило, хранятся в стеке времени выполнения. Стек представляет собой в основном большой массив памяти, который последовательно распределяется по локальным переменным и с одним индексом, указывающим на текущую "отметку о высокой воде". Этот указатель является указателем стека.

Когда функция вводится, указатель стека перемещается в одном направлении для выделения памяти в стеке для локальных переменных; когда функция завершается, указатель стека перемещается назад в другом направлении, чтобы освободить их.

Это означает, что фактическое местоположение локальных переменных в памяти определяется только со ссылкой на значение указателя стека при записи функции 1. Код в функции должен обращаться к локальным переменным через смещение от указателя стека. Точные смещения, которые будут использоваться, зависят от размера локальных переменных.

Теперь, когда все локальные переменные имеют размер, который фиксирован во время компиляции, эти смещения от указателя стека также фиксированы - поэтому они могут быть закодированы непосредственно в инструкциях, которые испускает компилятор. Например, в этой функции:

void foo(void)
{
    int a;
    char b[10];
    int c;

a может быть доступен как STACK_POINTER + 0, b может быть доступен как STACK_POINTER + 4, а c можно получить как STACK_POINTER + 14.

Однако, когда вы вводите массив с переменным размером, эти смещения больше не могут вычисляться во время компиляции; некоторые из них будут варьироваться в зависимости от размера, который массив имеет при вызове этой функции. Это значительно усложняет работу компиляторов, потому что теперь они должны писать код, который обращается к STACK_POINTER + N - и поскольку N сам изменяется, он также должен быть где-то сохранен. Часто это означает выполнение двух обращений - от одного до STACK_POINTER + <constant> для загрузки N, а затем для загрузки или сохранения актуальной локальной переменной, представляющей интерес.


1. Фактически, "значение указателя стека в записи функции" - это такое полезное значение, что у него есть собственное имя - указатель на фрейм, а многие ЦП предоставляют отдельный регистр, предназначенный для хранения указателя кадра. На практике это обычно указатель кадра, из которого вычисляется местоположение локальных переменных, а не самого указателя стека.

Ответ 2

Компилятор должен сгенерировать код для создания пространства для фрейма в стеке для хранения массива и других локальных локальных переменных. Для этого ему нужен размер массива.

Ответ 3

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

Есть, однако, две важные причины, почему это не в C89:

  • Код времени выполнения будет менее эффективным, если размер массива неизвестен во время компиляции.
  • Поддержка этого упрощает жизнь для компиляторов.

Исторически сложилось так, что C-компиляторы должны (относительно) легко писать. Кроме того, должно быть возможно сделать компиляторы простыми и достаточно маленькими, чтобы работать на скромных компьютерных системах (по стандарту 80-х годов). Еще одна важная особенность C заключается в том, что сгенерированный код должен быть чрезвычайно эффективным, без каких-либо неожиданностей,

Я думаю, что, к сожалению, эти значения больше не сохраняются для C99.

Ответ 4

Зависит от того, как вы выделяете массив.

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

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

Ответ 5

Скажем, вы создаете массив переменной размеров в стеке. Размер фрейма стека, необходимый функции, во время компиляции не будет известен. Таким образом, C предположил, что некоторые среды времени выполнения требуют, чтобы это было известно заранее. Отсюда и ограничение. C восходит к началу 1970-х годов. Многие языки в то время имели "статический" внешний вид (например, Fortran).

Ответ 6

В С++ это становится еще сложнее реализовать, поскольку переменные, хранящиеся в стеке, должны иметь свои деструкторы, вызываемые в случае исключения, или при возврате из заданной функции или области. Слежение за точным количеством/размером переменных, подлежащих уничтожению, добавляет дополнительные накладные расходы и сложность. В то время как в C можно использовать что-то вроде указателя фрейма, чтобы освободить VLA неявным, на С++, который вам не поможет, потому что эти деструкторы нужно вызвать.

Кроме того, VLA может привести к уязвимостям, связанным с уязвимостью в службе отказа в обслуживании. Если пользователь может предоставить любое значение, которое в конечном итоге используется как размер для VLA, тогда они могут использовать достаточно большое значение, чтобы вызвать переполнение стека (и, следовательно, отказ) в вашем процессе.

Наконец, у С++ уже есть безопасный и эффективный массив переменной длины (std::vector<t>), поэтому нет никакой возможности реализовать эту функцию для кода на С++.