В Си скобки действуют как кадр стека? - PullRequest
152 голосов
/ 03 мая 2010

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

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

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

Ответы [ 9 ]

82 голосов
/ 03 мая 2010

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

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

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

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

39 голосов
/ 04 мая 2010

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

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

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:

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

19 голосов
/ 03 мая 2010

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

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

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

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

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

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

6 голосов
/ 03 мая 2010

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

3 голосов
/ 03 мая 2010

Нет, 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 не совсем верна. В течение многих лет он сказал (выделение добавлено):

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

1 голос
/ 04 мая 2010

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

1 голос
/ 03 мая 2010

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

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

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

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

Обновление: Вот что говорит C spec . По объектам с автоматическим сроком хранения (раздел 6.4.2):

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

В том же разделе термин "срок службы" определен как (выделено мной):

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

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

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

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

0 голосов
/ 20 июля 2018

По стандарту уже предоставлено много информации о том, что он действительно зависит от реализации.

Итак, один эксперимент может представлять интерес. Если мы попробуем следующий код:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
        printf("%p\n", (void*) x);
    }
    {
        int b;
        y = &b;
        printf("%p\n", (void*) y);
    }
}

Используя gcc, мы получаем здесь два раза один и тот же адрес: Coliro

Но если мы попробуем следующий код:

#include <stdio.h>
int main() {
    int* x;
    int* y;
    {
        int a;
        x = &a;
    }
    {
        int b;
        y = &b;
    }
    printf("%p\n", (void*) x);
    printf("%p\n", (void*) y);
}

Используя gcc, мы получаем два разных адреса: Coliro

Итак, вы не можете быть уверены, что происходит.

0 голосов
/ 03 мая 2010

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

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