Вызов функции non-void без использования ее возвращаемого значения. Что на самом деле происходит?
Итак, я нашел аналогичный вопрос здесь, но ответы больше касаются стиля и того, сможете ли вы это сделать.
Мой вопрос: что на самом деле происходит, когда вы вызываете функцию non-void, которая возвращает объект, но вы никогда не назначаете или не используете указанный возвращенный объект? Итак, меньше о том, можете ли вы это сделать, потому что я абсолютно знаю, что вы можете и понять другой вопрос, связанный выше... что делает среда компилятора/среды выполнения?
Это не конкретный вопрос, но если вы ответите, укажите, на каком языке вы ссылаетесь, поскольку поведение будет отличаться.
Ответы
Ответ 1
Я считаю, что для С# и Java результат заканчивается в стеке, а затем компилятор заставляет pop-команду игнорировать его. Eric Lippert сообщение в блоге "Пустота является инвариантной" содержит дополнительную информацию об этом.
Например, рассмотрим следующий код С#:
using System;
public class Test
{
static int Foo() { return 10; }
public static void Main()
{
Foo();
Foo();
}
}
IL, сгенерированный (компилятором MS С# 4) для метода Main
:
.method public hidebysig static void Main() cil managed
{
.entrypoint
.maxstack 8
L_0000: call int32 Test::Foo()
L_0005: pop
L_0006: call int32 Test::Foo()
L_000b: pop
L_000c: ret
}
Обратите внимание на вызовы на pop
- которые исчезают, если вы делаете Foo
метод void.
Ответ 2
что делает компилятор?
Компилятор генерирует команду pop, которая отбрасывает результат из виртуального стека.
что делает среда выполнения?
Он обычно сжимает код в код, который возвращает возвращаемое значение обратно в регистр, а не в папку. (Обычно EAX на архитектуре x86.)
Джиттер знает, что значение останется неиспользованным, поэтому он, вероятно, генерирует код, который очищает регистр. Или, может быть, это просто заставляет его зависнуть в регистре какое-то время.
Какую среду выполнения вы интересуете? Их очень много, и у всех их есть разные неудобства.
Ответ 3
Это зависит от используемого соглашения о вызовах. Для малых/простых типов возврат обычно происходит в регистре. В этом случае функция будет записывать значение в регистр, но больше ничего не обратит внимания, и в следующий раз, когда этот регистр понадобится (что обычно произойдет довольно быстро), оно будет перезаписано чем-то другим.
Для более крупных типов компилятор обычно выделяет структуру для хранения возвращаемого значения. Точно там, где/каким образом это распределение будет варьироваться в зависимости от компилятора, в некоторых случаях это будет статическая структура, и содержимое будет проигнорировано и перезаписано при следующем вызове функции, возвращающей тот же тип. В других случаях он будет находиться в стеке, и даже если вы его не использовали, он все равно должен быть выделен до вызова функции и впоследствии освобожден
Ответ 4
В .NET, если возвращаемый объект является ссылочным типом, и у приложения нет других ссылок на этот объект, тогда у вас все еще будет объект, плавающий вокруг в памяти, пока сборщик мусора не решит его собрать.
Это потенциально опасно, если возвращаемый объект оказывается удержанием ресурсов. Если он реализует интерфейс IDisposable
, тогда ваш код должен вызывать метод Dispose на нем, но в этом случае метод Dispose никогда не будет вызываться.
EDIT: исправлена опечатка.
Ответ 5
Для С++, как правило, компилятор будет оптимизировать возврат переменной, превратив ее в функцию void, и если это позволит продолжить оптимизацию, компилятор может оптимизировать весь вызов функции или ее части, которые относятся только к возвращаемое значение. Для Java и С# у меня мало идей.