Вызов функции 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;
}

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


person Gio Borje    schedule 10.05.2011    source источник


Ответы (5)


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

person Xeo    schedule 10.05.2011
comment
Я думаю, вы имеете в виду конструктор, а не деструктор, поскольку деструктор вызывается в конце области видимости. - person Anton Golov; 10.05.2011
comment
Я только что прочитал о встроенных функциях: когда компилятор встроенно-расширяет вызов функции, код функции вставляется в поток кода вызывающего объекта. Если бы я встроил функцию второго примера, он будет отличаться от первого примера? - person Gio Borje; 10.05.2011
comment
Осторожно, компилятор встраивает вызов функции, а не вы. Даже если вы используете ключевое слово inline, это всего лишь подсказка для компилятора. Функция может быть встроена или нет во время компиляции, в зависимости от того, что в конечном итоге решит компилятор. - person zhaian; 10.05.2011

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

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

person zhaian    schedule 10.05.2011
comment
Если я скомпилирую оба приведенных выше примера, я предполагаю, что пример с новой областью действия будет микрооптимизирован в отличие от вызова функции из-за дополнительных накладных расходов, связанных с созданием нового фрейма стека< /я>. Разве это не преимущество? У меня могут быть некоторые ошибки в моей логике, но похоже, что есть разница в производительности (независимо от того, насколько она мала). - person Gio Borje; 10.05.2011
comment
Конечно, если вашей целью является только создание новой области, используйте фигурные скобки. Но почему вы хотите это сделать? Если это повторное использование имен переменных, я бы предостерег от этого. Если это попытка оптимизировать использование вашей памяти, я бы не стал пытаться управлять ею таким образом; компилятор должен быть в состоянии оптимизировать его достаточно хорошо. - person zhaian; 10.05.2011
comment
@Gio: В зависимости от уровня оптимизации (/O1 уже должно быть достаточно), этот вызов функции, скорее всего, будет встроен, в зависимости от его фактической длины и прочего. - person Xeo; 10.05.2011

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.

person c-smile    schedule 10.05.2011

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

    • passing variables is a pain, but sometimes it makes code more understandable to clearly indicate the smaller set of variables actually needed for the scoped operations (as well as encouraging operations to be grouped into discrete functional units, given an appropriate function name and context-relevant parameter names, and they're then ready for reuse)
  • внеочередные вызовы функций имеют несколько других накладных расходов на пространство стека и производительность: адреса возврата, сохраненные регистры, инструкции вызова и возврата.

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

    • reduced variable lifetime also reduces the number of concurrent variables the programmer has to mentally "track" to comprehend and maintain the code: the less the better
  • иногда нет особого смысла выбирать произвольные разные идентификаторы, когда вы выполняете набор похожих операций, поэтому некоторые локальные области позволяют удобно «перерабатывать» идентификатор.

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

person Tony Delroy    schedule 10.05.2011

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

person nikhil mehta    schedule 14.06.2015