시도에서 실제로 일어나는 일 {return x; } 마침내 {x = null; } 진술?


259

나는이 질문을 다른 질문에서 보았고 누군가 지구상에서 이것이 어떻게 작동하는지 설명 할 수 있는지 궁금해하고 있습니까?

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

내 말은, 그 finally절은 실제로 문장 다음return실행됩니까? 이 코드는 얼마나 안전하지 않은 스레드입니까? 이 try-finally해킹 으로 수행 할 수있는 추가 해커를 생각할 수 있습니까 ?

답변:


235

아니요-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;
}

10
이것이 정확히 ocdedio가 말한 것이 아닙니까 : 최종적으로 리턴 값을 계산 한 후 그리고 실제로 funczion에서 리턴하기 전에 실행됩니까?
mmmmmmmm 2018 년

"예외 처리 블록"이 시나리오는 예외 및 예외 처리와 관련이 없다고 생각합니다. 이것은 .NET이 최종 리소스 가드 구성을 구현하는 방법에 관한 것 입니다.
g.pickardou

361

finally 문은 실행되지만 반환 값은 영향을받지 않습니다. 실행 순서는 다음과 같습니다.

  1. return 문이 실행되기 전의 코드
  2. return 문의 표현이 평가됩니다.
  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"(반환 된 것이기 때문에)를 출력 한 다음 x의 새로운 값이기 때문에 "finally"를 출력합니다.

물론 변경 가능한 객체 (예 : StringBuilder)에 대한 참조를 반환하는 경우 finally 블록의 객체에 대한 변경 내용은 반환시 표시됩니다. 이는 반환 값 자체에 영향을 미치지 않습니다 (단지 참고).


묻고 싶습니다. Visual Studio에는 실행시 작성된 C # 코드에 대해 생성 된 중급 언어 (IL)를 볼 수있는 옵션이 있습니까? ...
Enigma State

예외 : "변경 가능한 객체 (예 : StringBuilder)에 대한 참조를 반환하는 경우 finally 블록의 객체에 대한 변경 사항은 반환시 표시됩니다."finallyBuild에서 StringBuilder 객체가 null로 설정된 경우, 이 경우 널이 아닌 오브젝트가 리턴됩니다.
Nick

4
@Nick : 그것은 객체에 대한 변화가 아니라 변수 에 대한 변화 입니다. 변수의 이전 값이 전혀 참조한 객체에는 영향을 미치지 않습니다. 따라서 예외는 아닙니다.
Jon Skeet

3
@Skeet "반환 문이 사본을 리턴 함"을 의미합니까?
prabhakaran

4
@ prabhakaran : return문장 의 시점에서 표현식을 평가하면 그 값이 반환됩니다. 컨트롤이 메서드를 벗어나면 표현식이 평가 되지 않습니다 .
Jon Skeet

19

finally 절은 return 문 뒤에 있지만 실제로 함수에서 반환되기 전에 실행됩니다. 스레드 안전성과는 거의 관련이 없다고 생각합니다. 그것은 핵이 아닙니다. 마지막으로 try 블록이나 catch 블록에서 무엇을 하든지 항상 실행됩니다.


13

Marc Gravell과 Jon Skeet의 답변에 덧붙여 객체와 다른 참조 유형은 반환 될 때 비슷하게 동작하지만 약간의 차이가 있습니다.

리턴되는 "What"는 단순 유형과 동일한 논리를 따릅니다.

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에 대해 고려해야 할 두 번째 사항은 "참조로"전달 된 매개 변수가 리턴 후에도 여전히 수정 될 수 있다는 것입니다. 만 반환 값이 평가되었으며 반환되는 임시 변수 대기에 저장되고, 다른 변수는 여전히 정상적인 방법을 수정됩니다. out 매개 변수의 계약은 최종적으로이 방법으로 차단 될 때까지 이행되지 않을 수 있습니다.

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;
        }
    }
}

다른 흐름 구성과 마찬가지로 "반환 최종 시도"는 그 자리에 있으며 실제로 컴파일 하는 구조를 작성하는 것보다 깔끔한 코드를 만들 수 있습니다 . 그러나 문제가 발생하지 않도록주의해서 사용해야합니다.


4

경우 x지역 변수이기 때문에, 나는 지점이 표시되지 않는 x효과적으로 방법은 종료됩니다 어쨌든 null로 설정하고 설정을 호출하기 전에 레지스터에 배치 된 이후 반환 값의 값 (null이 아닌 것 xnull로).

반환 할 때 (그리고 반환 값이 결정된 후) 필드 값의 변경을 보장하려는 경우에만이 일이 일어나는 것을 볼 수 있습니다.


지역 변수가 델리게이트에 의해 캡처되지 않는 한 :)
Jon Skeet

그런 다음 클로저가 있으며 여전히 참조가 있기 때문에 객체를 가비지 수집 할 수 없습니다.
재귀

그러나 나는 그 값을 사용하려고하지 않는 한 왜 델리게이트에서 로컬 var를 사용하는지 알지 못합니다.
재귀
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.