Почему компилятору C / C ++ нужно знать размер массива во время компиляции? - PullRequest
35 голосов
/ 03 декабря 2010

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

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

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]; 
}

Ответы [ 6 ]

69 голосов
/ 03 декабря 2010

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

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

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

Это означает, что фактическое расположение локальных переменных в памяти определяется только со ссылкой на значение указателя стека при вводе функции 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. Фактически, «значение указателя стека при входе в функцию» является настолько полезным значением, что оно имеет собственное имя - указатель кадра - и многие ЦП предоставляют отдельный регистр, выделенный для хранения указателя кадра. На практике это обычно указатель кадра, из которого вычисляется расположение локальных переменных, а не сам указатель стека.

6 голосов
/ 03 декабря 2010

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

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

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

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

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

5 голосов
/ 03 декабря 2010

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

3 голосов
/ 03 декабря 2010

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

3 голосов
/ 03 декабря 2010

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

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

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

2 голосов
/ 04 декабря 2010

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...