Что на самом деле происходит в try {return x; } наконец {х = ноль; } заявление? - PullRequest
244 голосов
/ 07 января 2009

Я видел этот совет в другом вопросе и задавался вопросом, может ли кто-нибудь объяснить мне, как на земле это работает?

try { return x; } finally { x = null; }

Я имею в виду, действительно ли предложение finally выполняет после оператора return? Насколько небезопасен этот код? Можете ли вы вспомнить какие-либо дополнительные хакерские атаки, которые можно сделать с помощью w.r.t. это try-finally взломать?

Ответы [ 5 ]

343 голосов
/ 07 января 2009

Оператор finally выполняется, но на возвращаемое значение это не влияет. Порядок исполнения:

  1. Код перед выполнением оператора возврата
  2. Выражение в операторе возврата оценивается
  3. наконец-то блок выполнен
  4. Возвращается результат, оцененный на шаге 2

Вот короткая программа для демонстрации:

using System;

class Test
{
    static string x;

    static void Main()
    {
        Console.WriteLine(Method());
        Console.WriteLine(x);
    }

    static string Method()
    {
        try
        {
            x = "try";
            return x;
        }
        finally
        {
            x = "finally";
        }
    }
}

Это печатает «try» (потому что это то, что возвращается), а затем «finally», потому что это новое значение x.

Конечно, если мы возвращаем ссылку на изменяемый объект (например, StringBuilder), то любые изменения, внесенные в объект в блоке finally, будут видны при возврате - это не повлияло на само возвращаемое значение (которое это просто ссылка).

224 голосов
/ 07 января 2009

Нет - на уровне IL вы не можете вернуться изнутри блока с обработкой исключений. По сути, он сохраняет его в переменной и возвращает потом

т.е. похож на:

int tmp;
try {
  tmp = ...
} finally {
  ...
}
return tmp;

например (с использованием отражателя):

static int Test() {
    try {
        return SomeNumber();
    } finally {
        Foo();
    }
}

компилируется в:

.method private hidebysig static int32 Test() cil managed
{
    .maxstack 1
    .locals init (
        [0] int32 CS$1$0000)
    L_0000: call int32 Program::SomeNumber()
    L_0005: stloc.0 
    L_0006: leave.s L_000e
    L_0008: call void Program::Foo()
    L_000d: endfinally 
    L_000e: ldloc.0 
    L_000f: ret 
    .try L_0000 to L_0008 finally handler L_0008 to L_000e
}

Это в основном объявляет локальную переменную (CS$1$0000), помещает значение в переменную (внутри обработанного блока), затем после выхода из блока загружает переменную и возвращает ее. Отражатель отображает это как:

private static int Test()
{
    int CS$1$0000;
    try
    {
        CS$1$0000 = SomeNumber();
    }
    finally
    {
        Foo();
    }
    return CS$1$0000;
}
19 голосов
/ 07 января 2009

Предложение finally выполняется после оператора return, но до фактического возврата из функции. Я думаю, это не имеет ничего общего с безопасностью потоков. Это не хак - гарантированно всегда будет работать finally, независимо от того, что вы делаете в блоке try или блоке catch.

13 голосов
/ 12 июля 2012

Добавляя к ответам, данным Марком Гравеллом и Джоном Скитом, важно отметить, что объекты и другие ссылочные типы ведут себя аналогично при возврате, но имеют некоторые различия.

Возвращаемое «Что» следует той же логике, что и простые типы:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        try {
            return ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
    }
}

Возвращаемая ссылка уже была оценена до того, как локальной переменной была назначена новая ссылка в блоке finally.

Исполнение по существу:

class Test {
    public static Exception AnException() {
        Exception ex = new Exception("Me");
        Exception CS$1$0000 = null;
        try {
            CS$1$0000 = ex;
        } finally {
            // Reference unchanged, Local variable changed
            ex = new Exception("Not Me");
        }
        return CS$1$0000;
    }
}

Разница в том, что все еще можно изменять изменяемые типы, используя свойства / методы объекта, что может привести к неожиданному поведению, если вы не будете осторожны.

class Test2 {
    public static System.IO.MemoryStream BadStream(byte[] buffer) {
        System.IO.MemoryStream ms = new System.IO.MemoryStream(buffer);
        try {
            return ms;
        } finally {
            // Reference unchanged, Referenced Object changed
            ms.Dispose();
        }
    }
}

Вторым моментом, который следует учитывать при использовании try-return-finally, является то, что параметры, передаваемые «по ссылке», все еще могут быть изменены после возврата. Только возвращаемое значение было оценено и сохранено во временной переменной, ожидающей возврата, все остальные переменные по-прежнему модифицируются обычным способом. Контракт выходного параметра может даже остаться невыполненным до тех пор, пока не будет окончательно заблокирован этот путь.

class ByRefTests {
    public static int One(out int i) {
        try {
            i = 1;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 1000;
        }
    }

    public static int Two(ref int i) {
        try {
            i = 2;
            return i;
        } finally {
            // Return value unchanged, Store new value referenced variable
            i = 2000;
        }
    }

    public static int Three(out int i) {
        try {
            return 3;
        } finally {
            // This is not a compile error!
            // Return value unchanged, Store new value referenced variable
            i = 3000;
        }
    }
}

Как и любая другая конструкция потока, «try-return-finally» имеет свое место и может обеспечить более чистый вид кода, чем при написании структуры, в которую фактически компилируется. Но его нужно использовать осторожно, чтобы избежать ошибок Гочи.

4 голосов
/ 07 января 2009

Если x - локальная переменная, я не вижу смысла, так как x будет эффективно установлен на ноль в любом случае при выходе из метода и значение возвращаемого значения не равно нулю (так как оно было помещается в регистр перед вызовом для установки x на ноль).

Я вижу, как это происходит, только если вы хотите гарантировать изменение значения поля по возвращении (и после определения возвращаемого значения).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...