Когда я компилирую следующий простой рекурсивный код с помощью g++, ассемблерный код просто возвращает i, как если бы g++ мог выполнять некоторые трюки алгебры, как люди.
int Identity(int i) {
if (i == 1)
return 1;
else
return Identity(i-1)+1;
}
Я не думаю, что эта оптимизация касается хвостовой рекурсии, и, по-видимому, g++ должен по крайней мере сделать две эти вещи:
- Если мы передадим отрицательное значение, этот код попадет в бесконечный цикл, поэтому допустимо ли для g++ устранить эту ошибку?
- Хотя можно перечислить все значения от 1 до INT_MAX, а затем g++ может сказать, что эта функция должна возвращать i, очевидно, g++ использует более умный метод обнаружения, поскольку процесс компиляции довольно быстрый. Поэтому моя проблема в том, как оптимизация компилятора делает это?
Как воспроизвести
% g++ -v
gcc version 8.2.1 20181127 (GCC)
% g++ a.cpp -c -O2 && objdump -d a.o
Disassembly of section .text:
0000000000000000 <_Z8Identityi>:
0: 89 f8 mov %edi,%eax
2: c3
Обновлено: спасибо многим за ответ на проблему. Я собрал некоторые обсуждения и обновления здесь.
- Компилятор использует некоторый метод, чтобы узнать, что передача отрицательных значений приводит к UB. Может быть, компилятор использует тот же метод для выполнения трюков алгебры.
-
О хвостовой рекурсии: Согласно Википедии, мой прежний код НЕ является формой хвостовой рекурсии.Я попробовал версию хвостовой рекурсии, и gcc генерирует правильный цикл while.Тем не менее, он не может просто вернуть меня, как мой прежний код. - Кто-то указывает, что компилятор может попытаться "доказать" f (x) = x, но я до сих пор не знаю названия используемой техники оптимизации. Я заинтересован в точном названии такой оптимизации, такой как устранение общего подвыражения (CSE) или какой-то их комбинации или чего-то еще.
Обновлено + ответил: Благодаря ответу ниже (я пометил его как полезный, а также проверил ответ от manlio), я думаю, я понимаю, как компилятор может сделать это простым способом. Пожалуйста, смотрите пример ниже. Во-первых, современный gcc может сделать что-то более мощное, чем хвостовая рекурсия, поэтому код преобразуется во что-то вроде этого:
// Equivalent to return i
int Identity_v2(int i) {
int ans = 0;
for (int i = x; i != 0; i--, ans++) {}
return ans;
}
// Equivalent to return i >= 0 ? i : 0
int Identity_v3(int x) {
int ans = 0;
for (int i = x; i >= 0; i--, ans++) {}
return ans;
}
(Я предполагаю, что) компилятор может знать, что ans и я разделяем одну и ту же дельту, и он также знает я = 0 при выходе из цикла. Следовательно, компилятор знает, что он должен вернуть i. В v3 я использую оператор >=
поэтому компилятор также проверяет знак ввода для меня. Это может быть намного проще, чем я предполагал.