2016-10-14 13 views
3

Рассмотрим следующий код:Как компилятор определяет необходимый размер стека для функции с генерируемыми временными циклами компилятора?

class cFoo { 
    private: 
     int m1; 
     char m2; 
    public: 
     int doSomething1(); 
     int doSomething2(); 
     int doSomething3(); 
} 

class cBar { 
    private: 
     cFoo mFoo; 
    public: 
     cFoo getFoo(){ return mFoo; } 
} 

void some_function_in_the_callstack_hierarchy(cBar aBar) { 
    int test1 = aBar.getFoo().doSomething1(); 
    int test2 = aBar.getFoo().doSomething2(); 
    ... 
} 

В строке, где getFoo() вызывается компилятор генерирует временный объект CFoo, чтобы быть в состоянии назвать doSomething1(). Является ли компилятор повторным использованием памяти стека, которая используется для этих временных объектов? Сколько памяти стека вызовет «some_function_in_the_callstack_hierarchy»? Он резервирует память для каждого созданного временного?

Мое предположение, что компилятор только резервирование памяти для одного объекта CFoo и повторное использование памяти для различных вызовов, но если добавить

int test3 = aBar.getFoo().doSomething3(); 

я могу видеть, что требуется размер стека для «some_function_in_the_callstack_hierarchy» больше и не только из-за дополнительной локальной переменной int.

С другой стороны, если я затем заменить

cFoo getFoo(){ return mFoo; } 

со ссылкой (только для целей тестирования, так как возвращает ссылку на частный член не хорошо)

const cFoo& getFoo(){ return mFoo; } 

он нужен путь меньше памяти стека, чем размер одного cFoo.

Так что для меня кажется, что компилятор резервирует дополнительную стек стека для каждого созданного временного объекта в функции. Но это было бы очень неэффективно. Может кто-нибудь объяснить это?

+3

Это в значительной степени определяется реализацией. Даже не гарантируется, что объекты с автоматическим хранилищем будут созданы в стеке в первую очередь. – SingerOfTheFall

+1

На всякий случай вы не убедились, что вы скомпилируете что-то вроде '-O2'. Анализ не оптимизированной сборки не очень полезен. – NathanOliver

+0

Скомпилировано с -O1 – Matthias

ответ

4

optimizing compiler преобразует исходный код в некоторое внутреннее представление и нормализует его.

С free software компиляторов (как GCC & Clang/LLVM), вы можете смотреть в это внутреннее представление (по крайней мере, исправляя код компилятора или запустить его в какой-то отладчик).

Кстати, временные значения иногда не требуют какого-либо пространства стека, например. потому что они были оптимизированы или потому, что они могут сидеть в регистрах. И довольно часто они будут повторно использовать некоторый ненужный слот в текущем кадре вызова. Также (особенно на C++) множество (небольших) функций - это inlined - например, ваш getFoo, вероятно, есть (так что у них нет кадровой системы). Недавние GCC равны иногда, способный к оптимизации tail-call (по существу, повторное использование кадра вызова вызывающего абонента).

Если вы скомпилируете GCC (т. Е. g++), я предлагаю поиграть с optimization options и developer options (и некоторыми другими). Возможно использовать also-Wstack-usage=48 (или другое значение, в байтах в кадре вызова) и/или -fstack-usage

Во-первых, если вы можете прочитать код на ассемблере, компилировать yourcode.cc с g++ -S -fverbose-asm -O yourcode.cc и посмотреть в излучаемого yourcode.s

(дон Не забудьте сыграть с флагами оптимизации, поэтому замените -O на -O2 или -O3 ....)

Затем, если вы еще хотите знать, как компилятор оптимизирует, попробуйте g++ -O -fdump-tree-all -c yourcode.cc, и вы получите много так называемые «дамп», которые содержат частичной текстовой визуализации внутренних представлений, имеющей отношение к НКУ ,

Если вы еще более любопытно, посмотрите в мою и особенно его documentation страницы GCC MELT (который содержит много слайдов & ссылок).

Так что для меня кажется, что компилятор резервирует дополнительную стек стека для каждого созданного временного объекта в функции.

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

ОТВЕТ: обратите внимание, что стандарт C++ 11 не говорит о стеке. Можно представить, что некоторые C++-программы скомпилированы без использования какого-либо стека (например, целая оптимизация программы, обнаруживающая программу без рекурсии, пространство стека и макет которой можно оптимизировать, чтобы избежать любого стека. Я не знаю такого компилятора, но я знаю, что компиляторы может быть довольно умными ....)

4

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

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

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

Редактировать: OP теперь вводит членов класса. Но поскольку они никогда не инициализируются и не являются private, компилятор может удалить их, не задумываясь об этом. Поэтому этот ответ по-прежнему применяется.

+0

Я забыл добавить некоторых членов в cFoo. Я отредактировал вопрос, так что экземпляр cFoo нуждается в памяти для переменных-членов. – Matthias

+0

Спасибо за недействительность моего ответа ;-). Он по-прежнему применяется, поскольку переменные никогда не инициализируются. – Bathsheba

+0

Это было бы справедливо, если бы они никогда не использовались.Но они используются в функции doSomething. Имейте в виду, что этот код предназначен только для вопроса. Я не могу показать вам свой настоящий код, потому что он принадлежит моему работодателю – Matthias

1

Срок службы временного объекта до конец полного содержащего выражения, см. Пункт «12.2 Временные объекты» стандарта.

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