Что происходит, когда переменная выходит за пределы области видимости?

#c #gcc

#c #управление памятью

Вопрос:

В большинстве управляемых языков (то есть тех, в которых есть GC) локальные переменные, которые выходят за пределы области видимости, недоступны и имеют более высокий GC-приоритет (следовательно, они будут освобождены первыми).

Итак, C не является управляемым языком, что происходит с переменными, которые выходят за пределы области видимости здесь?

Я создал небольшой тестовый пример на C:

 #include <stdio.h>
int main(void){
    int *ptr;

    {
        // New scope
        int tmp = 17;
        ptr = amp;tmp; // Just to see if the memory is cleared
    }

    //printf("tmp = %d", tmp); // Compile-time error (as expected)
    printf("ptr = %dn", *ptr);

    return 0;
}
  

Я использую GCC 4.7.3 для компиляции, и вышеприведенная программа печатает 17 , почему? И когда / при каких обстоятельствах локальные переменные будут освобождены?

Комментарии:

1. gcc 4.7.3 на сегодняшний день не выпущена. Должно быть, это 4.7.3 предварительный выпуск.

Ответ №1:

Фактическое поведение вашего примера кода определяется двумя основными факторами: 1) поведение не определено языком, 2) оптимизирующий компилятор сгенерирует машинный код, который физически не соответствует вашему C-коду.

Например, несмотря на то, что поведение не определено, GCC может (и будет) легко оптимизировать ваш код до простого

 printf("ptr = %dn", 17);
  

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

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

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

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

Однако, если вы сделаете это

 int main(void) {
  volatile int *ptr;
  volatile int *ptrd;

  { // Block
    int tmp = 17;
    ptr = amp;tmp; // Just to see if the memory is cleared
  }

  { // Sibling block
    int d = 5;
    ptrd = amp;d;
  }

  printf("ptr = %d %dn", *ptr, *ptrd);
  printf("%p %pn", ptr, ptrd);
}
  

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

Ответ №2:

Время жизни автоматического объекта заканчивается в конце блока, где он объявлен.

Доступ к объекту за пределами его жизненного цикла является неопределенным поведением в C.

(C99, 6.2.4p2) «Если на объект ссылаются за пределами его жизненного цикла, поведение не определено. Значение указателя становится неопределенным, когда объект, на который он указывает, достигает конца своего срока службы.»

Ответ №3:

Локальные переменные размещаются в стеке. Они не «освобождаются» в том смысле, в каком вы думаете о языках GC или памяти, выделенной в куче. Они просто выходят за пределы области видимости, и для встроенных типов код ничего не будет делать, а для объектов вызывается деструктор.

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