Вызов функции C++ против новых блоков для Push/Popping в стеке

Я читал о области видимости переменных в C++ и столкнулся с интересной структурой блоков:

int main(int argc, char **argv) {
    int local;

    { // New level of scope
        int more_local;
    }

    return 0;
}

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

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

void foo() {
    int more_local;
}

int main(int argc, char **argv) {
    int local;
    foo();

    return 0;
}

Как стек обрабатывается по-разному в обеих ситуациях и каковы преимущества и недостатки обоих?

5 ответов

Решение

Ну, вы могли бы сказать, что ваш первый пример можно рассматривать как встроенную функцию.:П
Но, как правило, вызовы функций и открытие нового scope не имеют ничего общего друг с другом.
Когда вы вызываете функцию, адрес возврата и все аргументы помещаются в стек и извлекаются из нее после возврата из функции.
При открытии нового scopeВы просто вызываете деструктор всех объектов в этой области в конце этого; ни в коем случае не гарантируется, что фактическое пространство, занимаемое этими переменными, сразу выталкивается из стека. Это возможно, но пространство может также просто использоваться другими переменными в функции, в зависимости от прихотей компиляторов / оптимизаторов.

При вызове функции вы помещаете адрес возврата в стек и создаете новый кадр стека. Если вы просто заключаете части кода в фигурные скобки, вы определяете новую область действия, как вы сказали. Они похожи на любой блок кода, следующий за оператором управления, например if, for, while и т. Д.

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

int more_local; будет помещен в стек в обоих случаях. Но во втором сценарии будут накладные расходы на вызов функции.

Я бы посоветовал вам подумать об этом:

void foo()
{
    int local;

    { // New level of scope
        int more_local_1;
    }
    { // New level of scope
        int more_local_2;
    }
}

Вот more_local_1 а также more_local_2 может использовать ту же область памяти Когда-то он использовался для more_local_1 и во второй сфере more_local_2 переменная.

  • локальные области все еще могут обращаться к другим локальным переменным, в то время как функции должны быть явно переданы любые переменные вызывающей стороны, которые они должны использовать

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

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

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

  • локальные области видимости немного неуклюжи и занимают "место на экране" в исходном коде, а также увеличивают уровень отступов, поэтому рекомендуется использовать их, когда есть конкретное обоснование, а не на основе "когда вы можете"

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

Другие вопросы по тегам