Вызов функции 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 и С# у меня мало идей.