Рассмотрим простой код.
void f() {
}
// ...
f();
Ясно, что функция f не делает ничего (точнее, не делает никакой полезной работы), поэтому компилятор может её спокойно "выбросить" без изменения поведения программы. (Вариант обращения к памяти за вершиной стека чтобы "сцапать" оттуда адрес возврата не берём, ибо "грязный хак", да ещё может и не сработать.)
Усложним задачу:
void g(int x) {
}
int calc_x() {
// Долго и упорно что-то вычисляем.
}
// ...
g(42); // Ни на что не влияет.
g(calc_x()); // А вот тут непонятно...
Ясно, что g(42) можно выкинуть. Но выкинуть g(calc_x()) в общем случае нельзя, поскольку у calc_x могут быть побочные эффекты.
Соответственно, вопрос: при каких условиях компилятор будет иметь право выбрасывать вызовы функции g?
P.S. Я понимаю, что компилятор может оставить даже вызов f(), если отключены все оптимизации. Вопрос следует понимать именно так, как написано - не выбросит, а имеет право выбросить
Ответ
Компилятор может превратить вызов g(calc_x()); в следующий [псевдо]код:
int x = ...;
calc_x код
x теперь чем-то проинициализирован.
Здесь мог бы быть код из g, но его там нет. Увы.
Т.е. компилятор заинлайнил обе функции, после чего он смотрит, что x нигде не используется и на этом этапе он может выкинуть код вообще.
Для примера, вот такой C++ код:
void g(int x) {
}
int calc_x() {
int x = 0;
while(x < 500)
{
++x;
}
return x;
}
int main()
{
g(42);
g(calc_x());
}
Превращается в вот такой ассемблер(и в студии, и в gcc):
main:
xor eax, eax
ret
Если попытаться вывести общую закономерность, то можно считать так: если компилятор может доказать, что код не имеет побочных эффектов, то он может его выкинуть. В противном случае он не имеет право его трогать. Простые вычисления, это код свободный от побочных эффектов, поэтому компилятор и выкидывает его, в примере выше. Но «может» не значит, что он его выкинет. Поэтому полагаться на это нельзя.
Комментариев нет:
Отправить комментарий