본문 바로가기

TechLog

Managed Type, Unmanaged Type의 참조

Managed Type, Unmanaged Type 참조

c# 에서의 값에 의한 전달은, c/c++에서의 값에 의한 전달과 약간 차이가 있습니다. c#에서 이런 차이가 나타날 때는 관리되는 형식(Managed Type)의 값이 전달될 경우에 일어나며, 관리되지 않는 형식(Unmanaged Type)일 경우엔 c/c++과 비슷한 동작을 하게 됩니다.

 

 

안녕하세요 : )

 

c++ 프로그래머 분께서 c#에서의 참조, ref 키워드 아 티클을 보셨다면 (그리고 아직 닷넷에 익숙하지 않으시다면) 약간 미심쩍은 부분을 발견하셨을 것입니다. 메서드를 통해 전달된 객체가 값에 의한 전달이든 참조에 의한 전달이든 상관 없이 같은 해시코드를 갖고 있다는, 다시 말하면, '같은 객체' 라는 점이죠.

 

이 아티클은 관리되는 형식과 관리되지 않는 형식에서의 차이점을 보여드리고, fixed 키워드에 대해서도 간략하게 설명하겠습니다.

 

 

c#에서는 값에 의한 전달을 사용해도 메서드 안에 있는 개체가 메서드 밖에 있는 개체와 같은 주소공간을 사용하는가?

 

먼저 c#에서의 참조, ref 키워드 아티클에서 나온 코드의 실행 결과를 보자 :

 

 

 

 

 

Create Foo
Foo() created.
init. hashcode : 13
 
in passingByValue()
in passingByValue() hashcode : 13
and create another one..
Foo() created.
in passingByValue() created hashcode : 14
and out passingByValue() hashcode : 13
 
in passingByRef()
in passingByRef() hashcode : 13
and create another one..
Foo() created.
in passingByRef() created hashcode : 15
and out passingByRef() hashcode : 15

 

 

 

 

  닷 넷에서 선언한 클래스는 관리되는 형식(Managed Type)으로 취급되어 해당 객체의 주소나 크기를 알 수 없게 된다. GetHashCode() 메서드를 사용하여 해당 객체가 메모리 상에서 동일한 위치에 있는지를 알아내려고 하는 것도, 해당 개체가 관리되는 형식이기 때문이다.

 

각각 값에 의한 전달과 참조에 의한 전달에 의해 객체가 전달되었음에도 불구하고 해당 객체의 해시코드 값은 13으로 나타난다. (System.Diagnostics.Debug.ReferenceEquals() 메서드로 확인해 보아도 메서드 안으로 전달된 개체와 메서드 밖의 개체가 동일함을 알 수 있다) 이는 gc(garbage collector) 하에서 개체가 관리되는 방식에 의한 특성이며, 개체를 값으로 전달했을 경우에도 개체의 복사를 하지 않고도 (생성자가 호출되지 않는 것으로 알 수 있다) 개체의 데이터를 이용할 수 있게 된다. 하지만 메서드 내부에서 개체를 변경하고자 할 때는 ref로 참조하여 전달해야만 해당 개체의 내부 변수(혹은 개체 그 자체)를 변경할 수 있게 해 준다.

 

잠시, 값에 의한 전달과 참조에 의한 전달을 비교할 수 있는 c 코드를 보도록 하자 :

 

 

 

 

void passingByValue(int i)
{
    printf("address of i in passingByValue() = %d\n",&i);
}
 
void passingByRef(int &i)
{
    printf("address of i in passingByRef() = %d\n",&i);
}
...
    int i = 0;
    printf("address of i = %d\n",&i);
    passingByValue(i);
    passingByRef(i);

 

 

 

 

 이 코드의 결과는 다음과 같다 (컴퓨터에 따라 값이 다를 수 있다) :

 

 

 

 

address of i = 1244884
address of i in passingByValue() = 1244672
address of i in passingByRef() = 1244884

 

 

 

 

c#에서도 이 결과는 동일하게 나타날 수 있지만, 해당 개체(보통은 변수)가 관리되지 않는 형식일 때만 가능하다. c#에서의 코드를 보자

 

 

 

 

int i = 0;
Debug.WriteLine(string.Format("{0}", (System.IntPtr)(&i)));
pVal(i);
pRef(ref i);
...
unsafe void pVal(int i)
{
    Debug.WriteLine(string.Format("{0}", (System.IntPtr)(&i)));
       
}
unsafe void pRef(ref int i)
{
    fixed(int *p = &i)
    {
        Debug.WriteLine(string.Format("{0}", (System.IntPtr)(p)));
    }
}

 

 

 

 

 

여 기에 나온 fixed 키워드는, 변수가 gc에 의해서 재할당되는 것을 막는 기능을 한다. pRef() 메서드로 실행 위치가 이동하면서, 예기치 않게 gc에 의해서 해당 변수가 수집될 가능성이 있기 때문이다. pVal() 메서드의 경우, int i가 새로 생성이 되기 때문에, 해당 scope에서는 i의 주소가 고정되므로 (=해당 scope에서는 gc에 의해 변수가 할당된 주소가 다시 수집되지 않으므로) fixed 키워드를 사용할 필요가 없다.

 

위 코드의 결과는 다음과 같다 (컴퓨터마다 결과가 다를 수 있다) : 

 

 

 

 

1240324
1240280
1240324

 

 

 

 

 

관 리되지 않는 형식을 사용하면 c 코드에서와 비슷한 결과를 얻을 수 있다.  결과처럼 int 변수에 대한 포인터를 얻을 수 있는 것은 int type이 관리되지 않는 형식이기 때문이다. msdn을 참고하면, 관리되지 않는 타입은 다음과 같다 :

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, or bool.

  • Any enum-type.

  • Any pointer-type.

  • Any user-defined struct-type that contains fields of unmanaged-types only.

 

 

정리

 

gc 하에서 관리되는 형식을 사용할 경우에는 해당 개체가 메서드에 전달되는 방식에는 상관 없이 개체의 데이터에 접근 가능하며, 데이터를 변경할 경우에만 참조 방식을 사용하면 된다는 것이 이 아티클의 결론입니다.

  

그럼 : )