Вызов функции 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
переменная.
локальные области все еще могут обращаться к другим локальным переменным, в то время как функции должны быть явно переданы любые переменные вызывающей стороны, которые они должны использовать
- Передача переменных - это боль, но иногда это делает код более понятным, чтобы четко указывать меньший набор переменных, фактически необходимых для операций с областью действия (а также способствует объединению операций в отдельные функциональные блоки при наличии соответствующего имени функции и релевантности контекста имена параметров, и они затем готовы для повторного использования)
вызовы функций вне линии имеют несколько других затрат стека и производительности: адреса возврата, сохраненные регистры, инструкции вызова и возврата
по сравнению с областью действия функции локальные области особенно хороши для минимизации области действия переменных, которые содержат важные ресурсы, такие как большие объемы памяти, потоки, файловые дескрипторы и / или блокировки: чем выше уровень и дольше выполняется функция, тем она полезнее может быть, чтобы убрать это быстро
- Сокращение времени жизни переменных также уменьшает количество параллельных переменных, которые программист должен мысленно "отследить", чтобы понять и поддерживать код: чем меньше, тем лучше
иногда не имеет особого смысла выбирать произвольно разные идентификаторы, когда вы выполняете набор похожих операций, поэтому некоторые локальные области позволяют удобно "перерабатывать" идентификатор
локальные области видимости немного неуклюжи и занимают "место на экране" в исходном коде, а также увеличивают уровень отступов, поэтому рекомендуется использовать их, когда есть конкретное обоснование, а не на основе "когда вы можете"
Если вы наблюдаете код ассемблера для обеих программ, то кажется, что нет никакой разницы, потому что компилятор, похоже, генерирует код ассемблера, чтобы выдвинуть новый кадр стека в текущий указатель стека, когда он сталкивается с открывающей фигурной скобкой или вызовом функции и выталкивает кадр из него. как только он встречает закрывающую фигурную скобку или оператор возврата. Преимущество во втором случае состоит в том, что вы можете вернуть значение с помощью оператора возврата в функцию вызывающей стороны. Но не в первом случае.