В C, скобки действуют как стек стека?

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

void foo() {
   int c[100];
   {
       int d[200];
   }
   //code that takes a while
   return;
}

Будет ли d занимать память в разделе code that takes a while?

Ответ 1

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

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

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

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

Ответ 2

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

Однако тесно связанный, но, возможно, более интересный вопрос заключается в том, разрешено ли программе получать доступ к этому внутреннему объекту вне внутренней области (но внутри содержащейся функции), то есть:

void foo() {
   int c[100];
   int *p;

   {
       int d[200];
       p = d;
   }

   /* Can I access p[0] here? */

   return;
}

(Другими словами: компилятор разрешает освобождать d, даже если на практике большинство из них не работает?).

Ответ да, компилятору разрешено освобождать d, а доступ к p[0], где комментарий указывает, - это поведение undefined. Соответствующая часть стандарта C - 6.2.4p5:

Для такого объекта [который имеет время автоматического хранения], которое делает не имеют тип массива переменной длины, его время жизни продолжается от входа в блок, с которым он связан пока выполнение этого блока не закончится любым способом. (Ввод закрытого блока или вызов функции приостанавливается, но не заканчивается, выполнение текущего блок.) Если блок введен рекурсивно, новый экземпляр объект создается каждый раз. начальное значение объекта неопределенный. Если инициализация для объекта, это выполняется каждый раз, когда декларация достигнутый при выполнении блока; в противном случае значение становится неопределенны каждый раз, когда декларация достигнута.

Ответ 3

Ваш вопрос недостаточно ясен, чтобы однозначно ответить.

С одной стороны, компиляторы обычно не выполняют локальное распределение памяти-освобождение для вложенных блоков. Локальная память обычно назначается только один раз при вводе функции и отпускается при выходе функции.

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

void foo()
{
  {
    int d[100];
  }
  {
    double e[20];
  }
}

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

Может ли последний квалифицироваться как d, продолжающий занимать память до конца функции в контексте вашего вопроса, для вас решить.

Ответ 4

Это зависит от реализации. Я написал короткую программу для проверки того, что делает gcc 4.3.4, и одновременно выделяет все пространство стека в начале функции. Вы можете проверить сборку, которую gcc производит, используя флаг -S.

Ответ 5

Нет, d [] не будет в стеке для остальной части подпрограммы. Но alloca() отличается.

Изменить: Кристофер Джонсон (и Симон и Даниэль) правы, и мой первоначальный ответ был неправильным. С gcc 4.3.4.on CYGWIN код:

void foo(int[]);
void bar(void);
void foobar(int); 

void foobar(int flag) {
    if (flag) {
        int big[100000000];
        foo(big);
    }
    bar();
}

дает:

_foobar:
    pushl   %ebp
    movl    %esp, %ebp
    movl    $400000008, %eax
    call    __alloca
    cmpl    $0, 8(%ebp)
    je      L2
    leal    -400000000(%ebp), %eax
    movl    %eax, (%esp)
    call    _foo
L2:
    call    _bar
    leave
    ret

Живи и учись! И быстрый тест, похоже, показывает, что AndreyT также правилен в отношении множественных распределений.

Добавлено намного позже. Вышеупомянутый тест показывает, что gcc documentation не совсем прав. В течение многих лет он сказал (выделено мной):

"Пространство для массива переменной длины освобождено, как только имя массива область завершается."

Ответ 6

Ваша переменная d обычно не удаляется из стека. Кудрявые скобки не обозначают стопку. В противном случае вы не сможете сделать что-то вроде этого:

char var = getch();
    {
        char next_var = var + 1;
        use_variable(next_char);
    }

Если фигурные скобки вызвали истинный стек push/pop (например, вызов функции), то приведенный выше код не будет компилироваться, потому что код внутри фигурных скобок не сможет получить доступ к переменной var, которая живет за пределами фигурных скобок (так же, как подфункция не может напрямую обращаться к переменным в вызывающей функции). Мы знаем, что это не так.

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

Обновление: Вот что должно сказать C spec. Что касается объектов с автоматической продолжительностью хранения (раздел 6.4.2):

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

В этом же разделе определяется термин "время жизни" как (основное внимание):

Время жизни объекта - это часть выполнения программы во время для которого зарезервировано хранилище . Объект существует, имеет постоянный адрес и сохраняет свое последнее сохраненное значение в течение его срок службы. Если объект упоминается вне его срока службы, поведение undefined.

Ключевое слово здесь, конечно, "гарантировано". Когда вы покидаете область внутреннего набора фигурных скобок, время жизни массива завершено. Хранение может или не может быть выделено для него (ваш компилятор может повторно использовать пространство для чего-то еще), но любые попытки доступа к массиву вызывают поведение undefined и приводят к непредсказуемым результатам.

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

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

Ответ 7

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

Ответ 8

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