나는이 질문을 다른 질문에서 보았고 누군가 지구상에서 이것이 어떻게 작동하는지 설명 할 수 있는지 궁금해하고 있습니까?
try { return x; } finally { x = null; }
내 말은, 그 finally
절은 실제로 문장 다음 에 return
실행됩니까? 이 코드는 얼마나 안전하지 않은 스레드입니까? 이 try-finally
해킹 으로 수행 할 수있는 추가 해커를 생각할 수 있습니까 ?
나는이 질문을 다른 질문에서 보았고 누군가 지구상에서 이것이 어떻게 작동하는지 설명 할 수 있는지 궁금해하고 있습니까?
try { return x; } finally { x = null; }
내 말은, 그 finally
절은 실제로 문장 다음 에 return
실행됩니까? 이 코드는 얼마나 안전하지 않은 스레드입니까? 이 try-finally
해킹 으로 수행 할 수있는 추가 해커를 생각할 수 있습니까 ?
답변:
아니요-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;
}
finally 문은 실행되지만 반환 값은 영향을받지 않습니다. 실행 순서는 다음과 같습니다.
다음은 간단한 프로그램입니다.
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 블록의 객체에 대한 변경 내용은 반환시 표시됩니다. 이는 반환 값 자체에 영향을 미치지 않습니다 (단지 참고).
return
문장 의 시점에서 표현식을 평가하면 그 값이 반환됩니다. 컨트롤이 메서드를 벗어나면 표현식이 평가 되지 않습니다 .
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;
}
}
}
다른 흐름 구성과 마찬가지로 "반환 최종 시도"는 그 자리에 있으며 실제로 컴파일 하는 구조를 작성하는 것보다 깔끔한 코드를 만들 수 있습니다 . 그러나 문제가 발생하지 않도록주의해서 사용해야합니다.
경우 x
지역 변수이기 때문에, 나는 지점이 표시되지 않는 x
효과적으로 방법은 종료됩니다 어쨌든 null로 설정하고 설정을 호출하기 전에 레지스터에 배치 된 이후 반환 값의 값 (null이 아닌 것 x
null로).
반환 할 때 (그리고 반환 값이 결정된 후) 필드 값의 변경을 보장하려는 경우에만이 일이 일어나는 것을 볼 수 있습니다.