Все ли локальные переменные создаются одновременно в начале выполнения функции в C?

#c

#c

Вопрос:

Допустим, у меня есть функция, подобная этой :

 int myfunc()
{
    int a;
    // do something
    int b;
    // do something
}
  

когда я вызываю myfunc(), происходит ли выделение памяти для b сразу после a? Или b создается после выполнения первого «сделать что-то».

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

1. Это может быть либо. Между ними нет заметной разницы. Поскольку ваша программа не может определить разницу, компиляторы могут использовать любой подход.

2. В спецификации C не указано, и на самом деле может не быть никакой памяти, выделенной ни для a , ни b . Они оба могут быть в регистрах или полностью оптимизированы. Чтобы увидеть, что происходит в конкретном экземпляре, скомпилируйте с -S и посмотрите на сборку.

3. Есть ли причина, по которой вы спрашиваете? Если вы наблюдаете разницу в поведении при определении здесь или там, то вы делаете что-то неправильно.

4. Если выделение памяти произойдет, это произойдет одновременно для всех локальных переменных. Указатель стека просто перемещает sizeof(a) sizeof (b) дальше, после чего начинает выполняться ‘do something’. Такого шага «создать» переменную не существует, компилятор генерирует код, который оставляет для нее некоторое пространство.

Ответ №1:

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

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

Учитывая программу

 #include <stdio.h>

int main(void) {
    int a = 0;

foo:;
    int b = 42;
    print("%d %dn", a, b);
    a   ;
    b   ;
    goto foo;
}
  

b будет иметь постоянный адрес в течение всего срока действия main функции, и так и есть a , но b повторноинициализируется после каждого goto .


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

Например, если у вас есть следующая программа:

 #include <stdio.h>

int main(void) {
    int a = 0;
    for ( ; a < 3; a  ) {
        printf("*");
    }
}
  

и вы спрашиваете «когда зарезервирована память для a «, тогда знайте, что и GCC, и Clang с помощью -O3 скомпилируют это в исполняемый файл, машинный код которого эквивалентен коду

 #include <stdio.h>

int main(void) {
    putchar('*');
    putchar('*');
    putchar('*');
}
  

Итак, когда a было «сконструировано»?

Ответ №2:

Стандарт C ничего не говорит об этом и оставляет это на усмотрение конкретной системы. Детали реализации.

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

С практической точки зрения, в большинстве систем (но не во всех системах) эти переменные на самом деле вообще не «распределены» — по крайней мере, не в обычном смысле.

Эти переменные находятся в «стеке», который часто (но не всегда) представляет собой одно выделение, выполняемое непосредственно перед началом выполнения потока.

Переменные (если они не оптимизированы) представляют часть «фрейма» стека функции, поэтому при каждом вызове функции она будет резервировать sizeof(int) * 2 для себя как часть своего фрейма стека.

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

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

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