나는이 질문을 다른 질문에서 보았고 누군가 지구상에서 이것이 어떻게 작동하는지 설명 할 수 있는지 궁금해하고 있습니까?
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 문은 실행되지만 반환 값은 영향을받지 않습니다. 실행 순서는 다음과 같습니다.
- return 문이 실행되기 전의 코드
- return 문의 표현이 평가됩니다.
- 마지막으로 블록이 실행됩니다
- 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 블록의 객체에 대한 변경 내용은 반환시 표시됩니다. 이는 반환 값 자체에 영향을 미치지 않습니다 (단지 참고).
답변
finally 절은 return 문 뒤에 있지만 실제로 함수에서 반환되기 전에 실행됩니다. 스레드 안전성과는 거의 관련이 없다고 생각합니다. 그것은 핵이 아닙니다. 마지막으로 try 블록이나 catch 블록에서 무엇을 하든지 항상 실행됩니다.
답변
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로).
반환 할 때 (그리고 반환 값이 결정된 후) 필드 값의 변경을 보장하려는 경우에만이 일이 일어나는 것을 볼 수 있습니다.