В C фигурные скобки действуют как стековый фрейм?

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

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

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

152
задан Claudiu 3 May 2010 в 16:02
поделиться

8 ответов

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

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

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

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

82
ответ дан 23 November 2019 в 22:12
поделиться

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

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

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

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

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

   return;
}

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

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

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

39
ответ дан 23 November 2019 в 22:12
поделиться

Нет, d [] не будет не в стеке до конца процедуры. Но alloca () другое.

Редактировать: Кристофер Джонсон (и Саймон и Дэниел) правы , и мой первоначальный ответ был неправильным . В gcc 4.3.4. на 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 не совсем верна. В течение многих лет он говорил (выделено автором):

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

3
ответ дан 23 November 2019 в 22:12
поделиться

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

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

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

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

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

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

19
ответ дан 23 November 2019 в 22:12
поделиться

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

1
ответ дан 23 November 2019 в 22:12
поделиться

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

0
ответ дан 23 November 2019 в 22:12
поделиться

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

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

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

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

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

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

В том же разделе термин «время жизни» определяется как (выделено мной):

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

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

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

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

1
ответ дан 23 November 2019 в 22:12
поделиться

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

6
ответ дан 23 November 2019 в 22:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: