Нет, varA
- это не постоянная времени компиляции - она может быть разной при каждом вызове функции. Константы имеют определенное определение в стандарте - некоторые ключевые данные касаются в this answer, или вы можете просто прочитать стандарт для официального слова.
Это означает, что вы можете знать, является ли компилятор обрабатывать как константу, в случаях, когда вы вызываете ее с постоянным значением, как в вашем примере. Ответ «да» для любого достойного компилятора с включенной оптимизацией. Вызов вставки и постоянное распространение - это волшебство, которое делает это возможным. Компилятор попытается включить вызов в foo
, а затем заменит 10
на аргумент и будет следовать за ним рекурсивно.
Давайте рассмотрим ваш пример. Я немного модифицировал его, чтобы использовать return foo (10) в main
, чтобы компилятор не полностью оптимизировал все целиком! Я также выбрал gcc __builtin_popcount
как неуказанную функцию, вызванную foo()
. Проверьте this godbolt version вашей программы без оптимизации, скомпилированной в gcc 6.2. Сборка выглядит так:
foo(int):
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], edi
mov eax, DWORD PTR [rbp-4]
popcnt eax, eax
pop rbp
ret
main:
push rbp
mov rbp, rsp
mov edi, 10
call foo(int)
pop rbp
ret
Это просто. Большинство из foo()
просто устанавливают фрейм стека и (бессмысленно) нажимают на стек edi
(аргумент varA
).
Когда мы вызываем foo()
из основного, мы передаем 10
в качестве аргумента. Так что ясно, что это константа не помогла.
ОК, давайте составим это с более реалистичным -O2
. Here's what we get:
main:
mov eax, 2
ret
Всё. Все это всего лишь return 2
, в значительной степени. Таким образом, компилятор определенно мог видеть, что 10 является постоянным значением и расширяет foo(10)
. Кроме того, он смог полностью оценить foo(10)
, вычислив popcount из 10 (0b1010 в двоичном формате) непосредственно, без необходимости инструкции popcount
и просто вернув ответ 2
.
Также обратите внимание, что компилятор даже не генерировал код для foo()
. Это потому, что он может видеть, что объявлено static inline
, поэтому его можно вызывать только из этой единицы компиляции и что на самом деле нет вызывающих абонентов, которым нужна полная функция, поскольку единственный сайт вызова был встроен. Так что foo просто исчезает.
Так что стандарт говорит о время компиляции константы только помогает в понимании того, что компилятор должен делать, и где некоторые выражения могут быть юридически используется, но это не очень помогает в понимании того, что компилятор будет делать на практике с оптимизацией.
Ключ здесь заключался в том, что ваш метод foo()
объявлен в том же компиляторе, что и его вызывающий, поэтому компилятор может встроить и эффективно оптимизировать две функции. Если бы это было в отдельном блоке компиляции, это не могло произойти, если вы не используете некоторые параметры, такие как генерация кода времени.
Как оказалось, в значительной степени любой оптимизация установки здесь результаты в том же коде, как преобразование довольно тривиально.
На самом деле, либо из inline
или static
достаточно, чтобы локальная функция в модуле компиляции. Однако, если вы опускаете оба, тело из foo()
будет создано, поскольку оно может быть вызвано из отдельно скомпилированного модуля. С оптимизацией тело выглядит так:
foo(int):
xor eax, eax
popcnt eax, edi
ret
Большое спасибо за такое подробное объяснение. – Arsen