전문가칼럼

DBMS, DB 구축 절차, 빅데이터 기술 칼럼, 사례연구 및 세미나 자료를 소개합니다.

닷넷 프레임워크, 정말 알고 있습니까 닷넷 CLR 이해하기

전문가칼럼
DBMS별 분류
DB일반
작성자
dataonair
작성일
2011-07-07 00:00
조회
15548




◎ 연재기사 ◎


닷넷 프레임워크, 정말 알고 있습니까 닷넷 CLR 이해하기


닷넷 프레임워크, 정말 알고 있습니까 닷넷 CLR 활용하기


닷넷 프레임워크, 정말 알고 있습니까 GC이해와 4.0의 CLR



닷넷 프레임워크, 정말 알고 있습니까

닷넷 CLR 이해하기

필자가 이번 연재를 통해서 전하고자 하는 것은 난해하거나, 최신의 기술이 아니다. 끝없이 많은 기술과 개발론이 터져 나오는 요즘, 필자를 포함한 우리는 기본에 대한 학습이 너무 부족하다. 아니, 여유가 없다고 해야 맞을 것이다. 특히 마이크로소프트에서 제공하고 있는 닷넷 프레임워크의 경우는 그 크기와 내용이 다른 플랫폼에 비해 결코 작지 않다.

WinFX라고 불리는 3.0 이후 버전부터 WPF, WCF등 막강한 주요 기술들의 등장으로 시작해 4.0으로 업데이트 되면서 패러렐 프로그래밍(Parallel Programming)요소를 비롯한 수많은 기능들이 추가되었고, 현시점에 닷넷 프레임워크를 이용하면 윈도우의 응용 레벨에서는 거의 모든 개발이 가능한 수준에 이르렀다.

하지만 정작 이를 접하는 사람들은 닷넷 프레임워크의 새로운 기술에는 열정적으로 목을 매면서도 닷넷 프레임워크 자체에 대해서는 많은 관심이 없다. 이런 안타까움에 필자는 지금까지 필자가 닷넷 프레임워크를 접하고 닷넷의 런타임에 대해 가졌던 의문과 그 의문을 풀어가는 과정을 이번 연재를 통해 공유하고자 한다.

필자가 프로그래밍이라는 단어를 처음 접했던 때는 대학교에 입학한 뒤였다. 대학에 입학하기 전까지는 단지 친구들과 컴퓨터 게임을 즐기거나 웹 서핑을 하는 컴맹에 가까운 컴퓨터 유저였다.

그렇게 대학에 입학해서 컴퓨터공학이라는 전공 타이틀을 달고 처음 접했던 언어는 C언어 였는데, 필자에게 C언어는 너무나 어려웠다.

알 수 없는 포인터와 더 이상 발전하지 않았던 콘솔기반의 개발은 필자를 지치게만 만들었다. 이후 접한 개발툴은 비주얼베이직(Visual Basic)이었다. 비주얼 베이직을 이용해 손쉽게 애플리케이션이 만들어지는 일련의 과정은 당시 C언어에 지쳐 있던 나에게 너무 신선한 충격이었다.

물론 그때 만들었던 애플리케이션들은 워낙 기본적인 것들이라 '손쉽게' 라는 표현이 가능한 것이겠지만 분명 다른 언어와 툴에 비해서 빠른 개발이 가능했던 것은 극명한 사실이다. 허나 시간이 꽤 지나고 나름대로 개발에 대한 견문을 넓힌 지금 누군가 '비주얼베이직이 쉽습니까'라고 묻는다면 함부로 '예'라고 대답할 수도 없을 것이다.

이러한 구도는 요즘에도 자주 보이곤 한다. 특히 일명 '네이티브'라고 불리는 언어를 사용하던 지인들이 비주얼 C#을 처음 접하면, 앞서 언급했던 것과 비슷한 성향의 '쉽다'라는 형태의 말들을 꺼내 놓고는 한다. 여기에 한 가지 더 '느리다'라는 의견 또한 이들의 대부분을 차지한다.

아마도 이 글을 읽는 대부분의 독자들은 비주얼 C#이 다른 네이티브 언어들과 비교해서 쉽다 혹은 느리다 라는 의견에 고개를 끄덕일 것이다. 물론 필자도 이 의견에 동의했다.

하지만 막연히 동의하기에는 엔지니어로써 어떠한 논리적인 설명이 필요할 것이다. 그 논리를 찾기 위해서 공부를 하기 시작했고 이 과정에서 필자는 의문을 가지기 시작했다.

세상에 배우기 쉬운 학문이 어디 있으며, 지금 시점의 하드웨어 성능을 고려했을 때, 닷넷기반으로 개발된 결과물이 느리다면 그것이 플랫폼의 탓일까 물론 상대적으로 쉽거나 느릴 수 있겠으나 이것을 완전하게 플랫폼이나 환경의 탓으로 돌리기에는 분명 석연치 못한 부분이 있다.

왜 CLR을 알아야 하는가

이러한 의문점을 해결하기 위해서는 어디서부터 이야기를 전개해 나가야 할끼 우선 가장 먼저 할 일은 작업하는 환경의 전반적인 모습을 알고 머릿속에 그려내는 것이 중요하다. 이것은 어떠한 분야를 공부하더라도 통용될 수 있는 학습법이라 생각된다.
우리가 논하고자 하는 닷넷 프레임워크의 기본적인 아키텍처를 살펴보자. <그림 1>은 닷넷에 관심이 있거나, 닷넷을 이용한 개발을 해본 사람이라면 한 번쯤은 보았을 법한 그림이다.

110706_ok3_img01.png
<그림 1>닷넷 프레임워크 기본 아키텍처

모두가 알고 있는 것처럼 닷넷 프레임워크에서 사용 가능한 언어의 종류는 생각보다 다양하다. 닷넷 프레임워크가 지원하는 규약을 따르기만 한다면 어떤 언어이든 닷넷 프레임워크위에서 동작할 수 있게 된다.
여기서 각 언어들이 지켜야 하는 규약이 바로 공용 언어 사양(Common Language Specification, 이하 CLS)이다. 그 아래 레이어에 우리가 알고 있는 ASP닷넷, WinForm(Windows Forms)과 같은 계층이 존재한다.

개발 성향에 따라서 적절한 플랫폼을 선택하게 되고 이렇게 선택한 개발 플랫폼과 운영체제 사이에 닷넷의 런타임이 존재한다. 여기서 언급한 런타임은 공용 언어 런타임(Common Language Runtime, 이하 CLR)이라고 불리는데 바로 여기에 지금 우리가 알아보고자 하는 닷넷에 대한 실마리가 담겨있다.

닷넷 프레임워크에서 CLR

CLR은 닷넷 프레임워크의 런타임이라고 했다. 앞서 닷넷 프레임워크에서는 여러 가지 언어로 개발이 가능하다고 언급했는데, 이는 CLR과 CLS의 존재로 인해서 서로 다른 언어로 개발된 명령어들을 어떤 과정을 거쳐 하나의 통합된 양식의 명령어로 변환하고 이를 CLR이 수행하게 되는 것을 말한다.

이 과정에 대해서는 차근차근 살펴보기로 하자. 또 한가지 CLR의 큰 특징이 존재하는데 바로 메모리 관리다.
Managed라는 키워드가 닷넷에 붙게 된 가장 큰 원인을 제공하는 존재이기도 하다. 프로그래머를 메모리 관리에서 해방시켜 주었지만 결정적으로 닷넷의 '느리다'라는 이미지를 가중시키는데 한 몫하고 있기도 하다. 이러한 장치를 우리는 Garbage Collector라고 부른다. Garbage Collector에 대해서도 차근차근 살펴보기로 하자. 지금은 CLR의 존재 그 자체에 초점을 두면 되겠다. 그리고 왜 Common Language라고 불리며, 이러한 기능을 어떻게 소화해 내는지 간단한 데모를 통해서 직접 눈으로 확인해보도록 하자.

Common Language

Common Language를 직역하면 '공용 언어'다. 여기서 말하는 공용 언어는 닷넷의 런타임이 받아들이는 언어를 뜻한다.
이것을 우리는 중간 언어(Intermediate Language, 이하 IL코드)라고 부른다. 닷넷에서 사용되는 IL코드는 좀 더 명확하게 MSIL(이하 본문에서는 MSIL을 IL코드로 호칭)이라고 호칭된다.

바로 이 녀석이 결과적으로 런타임이 수행할 수 있는 최종단계에 가까운 명령어가 되는 것이다. 앞서 언급했던 '하나의 통합된 양식'이 바로 IL코드이다. 그렇다면 닷넷의 런타임을 위한 IL코드가 서로 다른 언어로부터 어떻게 동일한 양식으로 변환되고 관리되는지 확인해보도록 하자.

CLS(Common Language Specification)

닷넷 프레임워크는 여러 가지 언어로 코드를 작성하는 일이 가능하다. 하지만 언어마다 고유한 문법이 존재하는데 이런 부분을 극복하기 위해 존재하는 계층이 앞서 보았던 아키텍처 상에서 CLS라고 명명되어 있는 계층이다.
실제로 아키텍처에서 언어들의 바로 아래 위치면서 닷넷기반 개발에 있어 언어마다 존재하는 차이점을 통합된 양식으로 변환시켜주는 작업이 가능하도록 하는 일종의 '약속'과 같은 것이다.
직접적인 예로 <리스트 1>과 같이 C#으로 작성된 클래스를 살펴보자.

<리스트 1> C#으로 작성된 클래스

using System;
namespace CLS_Demo_CSharp
{
public class MyClass
{
public int IntValue = 0;
public UInt32 UIntValue = 0;
public String StrValue = String.Empty;
public void Method()
{
}
public void METHOD()
{
}
}
}

<리스트 1>의 코드는 C#의 문법적으로 아무런 문제가 없는 코드다. C#은 32bit unsigned integer를 지원하며 String이라는 문자열 타입도 지원한다. 또한 메소드의 정의 역시 시그니처가 동일하고, 메소드의 알파벳이 같더라도 대·소문자가 다르면 서로 메소드로 취급하게 되어있다. 그렇다면 <리스트 1>의 코드를 <리스트 2>와 같이 수정해 보자.

<리스트 2> CLSCompliant Attribute적용

using System;
[assembly: CLSCompliant(true)]
namespace CLS_Demo_CSharp
{
public class MyClass
{
public int IntValue = 0;
public UInt32 UIntValue = 0;
public String StrValue = String.Empty;
public void Method()
{
}
public void METHOD()
{
}
}
}

<리스트 3> CLS 검사 결과 발생한 경고들

경고 CS3003: //UInt32 변수 사용에 대한 경고
'CLS_Demo_CSharp.MyClass.UIntValue'의 형식이 CLS 규격이 아닙니다.
경고 CS3005: //Method 정의 방식에 대한 경고
대/소문자만 다른'CLS_Demo_CSharp.MyClass.METHOD()'식별자가 CLS
규격이 아닙니다.

<리스트 2>의 코드를 컴파일하면 에러는 없다. 하지만 2개의 Warning이 발생한다. 발생하는 Warning의 내용은 <리스트 3>과 같다.

이와 같은 경고가 발생하는 이유는 CLS에 대한 명세에서 파생된다. 더 쉽게 설명하면, 어떤 언어는 UInt32를 지원하지 않는 경우가 있고 또 어떤 언어는 메소드의 구분을 시그니처가 동일하고, 대·소문자가 다르더라도 동일한 메소드로 취급하는 언어가 존재한다.

이러한 언어들 간의 차이점을 극복하고 동일한 동작을 보장하기 위한 규격이 바로 CLS다.

실제로 우리는 CLSCompliant라는 Attribute를 적용하여 이러한 규격에 대한 검사를 수행하고 CLS라는 것이 실제로 존재함을 살펴 볼 수 있었다. 단, 한 가지 주의할 점은 위 코드에서 클래스 및 멤버의 접근제한을 private으로 지정하게 되면 CLS에 대한 규격 검사는 무효하다. 왜냐하면 접근제한을 private로 지정하게 되면 라이브러리 형태로 개발하게 된다고 해도 닷넷기반의 다른 언어에서 불러와 사용할 수 없기 때문이다.

궁금증을 가진 독자들이라면 <리스트 2>의 클래스 앞에 public 키워드만 제거한 뒤 다시 컴파일하면 결과를 확인할 수 있을 것이다. 만약 독자가 닷넷기반의 라이브러리 형태의 것을 개발하는 개발자라면 반드시 배포이전에 CLS에 대한 규격검사를 수행하는 작업을 하는 것이 유리할 것이다.

CLS라는 규격이 수행하는 작업은 위와 같은 단순한 검사에서 끝나지 않는다. C#의 경우 언어만의 고유한 문법이나 키워드들이 존재한다.

예를 들어 property, event, delegate등이 있을 수 있다. 하지만 다른 언어에는 존재하지 않는다.

닷넷에서는 여러 언어로 코드를 작성할 수 있으나 런타임은 CLR하나다. 앞에서 이런 문제점을 극복하기 위해서 닷넷기반의 언어로 작성된 코드는 하나의 통합된 양식의 명령어로 변환한 후, 이를 CLR이 실행한다고 했다.

여기서 통합된 양식의 명령어를 우리는 중간 단계 언어(Intermediate Language, 이하 IL)라고 부른다. 닷넷에서 사용되는 IL코드는 MSIL이라는 이름으로 불리는데 본문에서는 IL과 MSIL을 구분하지 않고 동일한 용어로 보도록 하자.

백문이 불여일견, 실제로 IL코드가 어떻게 생겼는지 살펴보자. 이를 확인하기 위해서 역어셈블러를 사용하여 컴파일한 어셈블리를 열어볼 것이다. 역어셈블러의 경우 비주얼 스튜디오와 함께 배포되는 역 어셈블러가 존재한다.
그 외에도 닷넷 Reflector와 같은 상당히 다양한 기능을 제공하는 역어셈블러 또한 존재하니 검색을 통해서 한 번쯤은 사용해 보면 학습에 많은 도움이 된다.

비주얼 스튜디오에서 제공되는 역어셈블러는 비주얼 스튜디오 툴에서 비주얼 스튜디오 명령 프롬프트를 실행한 후, ildasm라는 키워드를 통해서 실행 가능하다.

110706_ok3s_img01.png
<화면 1> IL DisAssembler 실행 화면

이번에는 실행된 IL DASM에서 '파일 → 열기' 기능을 이용해 앞에서 컴파일 했던 어셈블리를 열어보자.

110706_ok3s_img02.png
<화면 2> 컴파일한 어셈블리 열어보기

그러면 <화면 3>과 같이 우리가 작성했던 코드의 역어셈블된 결과를 확인해 볼 수 있다.

110706_ok3s_img03.png
<화면 3> MyClass의 역어셈블 결과 및 MyClass::METHOD()의 IL코드

역어셈블하는 방법을 알았으니, 이제 앞에서 언급했던 언어만의 고유한 문법이 어떻게 IL코드로 통합되는지 확인하기 위해 아래와 같은 C#코드를 작성해보자.

<리스트 4> 각각 Event와 Property를 사용한 클래스

using System;
namespace DASM_Demo
{
delegate void EventDelegate();
class EventClass
{
public event EventDelegate Event;
public void EventHandler()
{
Event();
}
}
class PropertyClass
{
private int IntValue = 0;
public int Value
{
get
{
return IntValue;
}
set
{
if (value >= -100 && value <= 100)
IntValue = value;
else IntValue = 0;
}
}
}
class Program
{
static void Main(string[] args)
{
EventClass EventInstance = new EventClass();
EventInstance.Event += new EventDelegate
(EventInstance_Event);
EventInstance.EventHandler();
PropertyClass PropertyInstance = new
PropertyClass();
PropertyInstance.Value = 99;
}
static void EventInstance_Event()
{
Console.WriteLine("Event!");
}
}
}

이제 <리스트 4>의 코드를 하자. 그러면 <화면 4>와 같은 결과를 확인할 수 있을 것이다.

110706_ok3s_img04.png
<화면 4> 역어셈블 해본 <리스트 4>의 어셈블리

<화면 4>에서 표시된 부분을 주의 깊게 살펴보자. EventClass와 PropertyClass에서 우리가 선언/정의하지 않았던 <리스트 5>와 같은 메소드가 각각 생성되었다.

<리스트 5> IL코드 변환 과정에서 생성된 메소드

[EventClass]
add_event : void(class DASM_Demo.EventDelegate)
remove_event : void(class DASM_Demo.EventDelegate)
[PropertyClass]
get_Value : int32
set_Value : int32

<리스트 5>에 명시된 새롭게 생성된 메소드들은 지금까지 계속 언급해왔던 여러 언어들 간의 통합을 위해 IL코드로 변환하는 과정에서 C#에서 제공했던 추상적인 개념인 event와 property를 개념에 맞는 메소드 형태로 변환된 것이다.

Event의 경우는 delegate를 캡슐화한 형태인 add_event, remove_event라는 이름의 메소드로 변환되었고, Property의 경우 역시 멤버변수를 캡슐화한 형태인 get_Value, set_Value라는 이름으로 바뀌었다.

결국 특성에 맞게 add/remove, get/set를 각각 prefix한 메소드로 변환되었다. CLS는 이렇게 닷넷 기반의 언어들이 IL코드로 변환되고, CLR이 수행할 수 있는 명령어로 변환하는 과정까지 명시하고 있다.

이쯤되면 독자들도 아키텍처 상에서 CLS라는 계층이 왜 명시적으로 표시되어 있고, 이것이 어떤 역할을 하는지 어렴풋이 느낌이 왔을 것이라 생각된다.

CLR의 동작원리

그럼 이제부터 CLR에 대한 이야기를 이어서 해보자. CLR의 역할 중 하나는 런타임으로서의 역할이며, 다른 하나는 메모리 관리의 역할이다. 사실 이 두 가지를 분리해서 보는 것은 이치에 맞지 않다.

메모리를 관리하는 과정이 런타임의 수행과정에 직접적으로 영향을 주기 때문이다. 좀 더 풀어 설명하자면 메모리 관리 스레드가 런타임의 스레드에 직접적으로 영향을 준다는 이야기다. 그렇다면 메모리 관리가 어떻게 이뤄지는지, 그리고 이 둘의 스레드가 어떤 관계에 있는지 알아보자.

또한 지금부터는 메모리 관리라는 용어 대신에 실제로 이를 수행하는 Garbage Collector(이하 GC)라는 용어를 사용하도록 하겠다.

닷넷 프레임워크에서 GC가 어떤(What) 작업을 수행하는지는 이미 모두가 너무나도 잘 알고 있을 것이다. 하지만 GC가 어떻게(How) 메모리 관리라는 작업을 수행하게 되는지 아는 사람은 생각보다 많지 않다.
그리고 여기서 '어떻게(How)'를 알게 되면 이때부터 왜 닷넷기반의 결과물이 상대적으로 네이티브 언어로 개발된 결과물보다 느린가 알 수 있게 되며, 이로써 닷넷기반 개발 과정에서 성능향상을 위한 코드 작성에 실마리가 풀리기 시작한다.

GC 메모리에 접근하는 기본적인 방식은 <그림 2>과 같은 기본적인 포인터 증가연산에서 시작된다.

110706_ok3_img02.png
<그림 2>GC의 NextObjPtr

우리는 여기서 한 가지 의문을 가질 수 있다. 단순한 포인터 증가 연산이라면 C/C++의 런타임 처럼 메모리 검사를 할 필요 없이 단순하게 포인터를 증가시켜 할당하기만 하면 되기 때문에 느려질 이유가 없다.

또한 메모리가 조각나거나 하는 등의 걱정도 훨씬 줄어들며 일반적으로 CPU가 제공하는 명령어를 통해서 작업을 마칠 수 있게 되기 때문에 메모리 할당 자체의 성능은 매우 뛰어나게 된다.

그렇다면 'C/C++의 런타임보다 닷넷의 CLR이 훨씬 빨라야 정상이 아닐까' 라는 순간적인 착각을 불러일으킬 수 있다. 하지만 이와 같은 논리는 한 가지 가정 하에 성립될 수 있다.

다름 아닌 '메모리의 공간이 무한하다'라는 것 때문인데, 닷넷에서 이런 전제를 만족시켜줄 수 있는 장치인 GC가 존재한다.
GC의 기본적인 방식은 '선형 메모리 할당'과 '사용하지 않는 메모리 제거'로 구성되어 있다는 점이다. 또한 단순 포인터 증가 방식으로 필요한 만큼의 메모리를 할당해 가는 것이 '선형 메모리 할당'이다.

두 가지 방식을 기반으로 메모리가 수집되는 과정을 순차적으로 살펴보자면 <그림 3>을 기준으로 다음과 같은 방식으로 이뤄진다.

110706_ok3_img03.png
<그림 3> GC가 관리하는 메모리

1. 새로운 객체를 적재해야 할 경우 메모리가 부족하지 않으면 새로운 객체를 현재까지 적재된 바로 다음 위치에 적재하고 NextObjPtr을 증가 (Linear Memory Allocation)

2. 객체 적재 중 메모리가 부족하다고 판단되면 메모리 수집을 시작하기 위해 모든 객체를 Garbage로 가정

3. 적재가 시작되었던 메모리부터 순차적으로 객체 순회를 시작

4. 참조가 이뤄지고 있는 객체에 대해서 Graph작성 (정확한 의미에서는 Reference Graph)

5. 순회중 Object D와 같이 다른 객체를 참조하고 있으면 참조하고 있는 객체 역시 Graph에 추가 (여기서는 Object D에 이어서 Object H 추가)

6. 동일한 방식으로 도달 가능한 모든 객체를 재귀적으로 순회

7. 순회 중 Graph에 추가된 객체를 다시 만날 경우 순회 중단

8. 모든 객체를 한 번씩만 순회하여 최적화 (<그림 3>의 점선으로 표시된 원과 같은 무한루프 방지)

9. 순회가 끝나면 만들어진 Graph에 존재하지 않는 객체를 Managed Heap에서 해제

10. Graph가 아닌 데이터를 빈틈없이 재배열

11. Application 영역에서 참조하고 있는 객체 및 객체간 참조가 이뤄지고 있는 포인터를 수정

12. NextObjPtr 업데이트


이와 같은 과정을 좀 더 그럴싸한 용어로 표현하자면 Memory Compaction이라고 한다. 말 그대로 '간결화 또는 압축'하는 것이다.

실제 GC가 동작하는 과정은 이보다 훨씬 복잡하지만 기본적인 동작 원리는 설명한 내용에서 크게 벗어나지 않는다.

기본적이라고는 했지만, 앞선 과정에서 알 수 있듯이 GC가 수집을 시작하게 되면 생각보다 많은 작업이 발생하게 된다.

메모리를 순회하고, 그리고 수집 대상으로 지정된 메모리를 해제하고, 마지막으로 정리된 메모리를 재배열하는 과정은 Heap Size를 고려하면 결코 무시하기 힘든 수준의 작업이다(엄밀히 말하면 모든 상황에서 GC의 수집 작업이 우려할 수준의 오버헤드를 발생키는 것은 아니다).

또한 메모리에 변화가 일어나기 때문에 GC가 수집을 하는 도중에는 애플리케이션의 스레드 또한 동작을 할 수 없을 것이라는 것도 어렵지 않게 추측해 낼 수 있다. 따라서 잦은 GC의 수집 작업은 프로그램의 성능 저하에 매우 치명적으로 작용하게 된다.

하지만 GC라는 장치 자체로써 우리는 매우 많은 편의를 얻고 있다. 메모리 관리 해방으로 인한 생산성의 증가, 개발 기간의 단축, 프로그램의 안정성 등을 상대적으로 높게 가져갈 수 있게 되었다.

GC의 수집 작업은 큰 맥락에서 두 가지 상황으로 발생한다. 하나는 Managed Heap이 가득 차게 되었을 때와 같이 자체적으로 수집이 필요하다고 판단되는 경우이며, 또다른 하나는 코드에서 개발자가 명시적으로 GC의 수집을 명령했을 때다.

가끔 개발하는 지인들을 보면 코드 내에서 GC.Collect()와 같이 명시적으로 GC에게 수집을 명령하는 코드가 남용되어 있는 경우를 보곤 하는데, 이는 그리 좋은 습관은 아니다.

물론 필요에 의해서 GC.Collect()와 같이 GC에게 수집을 명령하는 메소드를 제공하고 있겠지만 GC의 동작을 명확하게 이해하지 못한 상황에서 이를 무작위로 남용하는 것은 말했던 것과 같이 프로그램의 성능 저하에 매우 치명적인 독과 같다.

결국 Type이다

그렇다면 이러한 GC동작 매커니즘의 대상이 되는 것은 무엇일까 결국 각종 GC동작 매커니즘의 대상이 되는 것은 메모리에 존재하는 객체이다. 객체라는 것은 타입에서 실체화된 존재라는 것을 우리는 귀가 따갑도록 들어왔다. 그렇다면 닷넷 CLR에서의 타입은 어떻게 다뤄질까
<리스트 6>의 결과를 기반으로 닷넷 CLR에서 다루어지는 타입이 어떻게 다뤄지는지 한번쯤 추측해보자(사실은 이번 연재에서 이에 대한 답이 될 수 있는 키워드를 우리는 계속해서 사용했다).

<리스트 6> 매개변수로써 객체를 전달하고, 전달된 객체에 접근하는 C# 코드

using System;
namespace CLR_Demo
{
class Program
{
static void Main(string[] args)
{
MyClass obj = new MyClass();
obj.PrintValue();
SetValue(obj, 1); //Method Call
obj.PrintValue();
}
static void SetValue(MyClass obj, int n) //Pass by
value reference
{
obj.Value = n;
}
}
class MyClass
{
private int nValue = 0;
public int Value
{
get
{
return nValue;
}
set
{
nValue = value;
}
}
public void PrintValue()
{
Console.WriteLine("Value : " + Value);
}
}
}

일반적으로 메소드의 매개변수로 사용되는 Type들은 Pass by value로 인식하고 있는 경우가 많다. 하지만 <리스트 6>을 실행시켜 결과를 확인하면 값이 '1'로 바뀌어 출력되는 것을 확인할 수 있다.

사실 이런 부분은 어느 정도 닷넷 개발을 경험한 사람이라면 학습을 통해서든 경험을 통해서든 알고 있는 부분이다. 하지만 닷넷에서 객체가 기본적으로 Pass by reference로 다뤄지는 것이 GC의 동작에 상당히 깊은 연관이 있다는 것을 눈치채는 개발자는 생각보다 많지 않다(계속해서 '참조'라는 키워드를 이번 글에서 사용했다).

여기까지의 글로써 닷넷 CLR의 주요 두 가지 기능(런타임, Managed)에 대해서 독자들이 이해를 하고 있다면 다음 연재에 등장할 내용을 소화하는데 무리가 없을 것이라고 판단된다.

Type과 닷넷 CLR의 동작과 관련된 내용은 다음 컬럼에서 본격적으로 이야기해 볼 것이다. 또한 앞서 의문으로 남겨두었던 더 복잡하고 자세한 GC의 동작에 대해서도 이 부분들과 연관지어 함께 알아볼 수 있도록 할 것이다.

사실 필자가 언급하고 있는 모든 내용은 MSDN이나 서적, 검색 등을 통해서 어렵지 않게 접할 수 있는 내용들이 대부분이다. 하지만 많은 닷넷 개발자들이 런타임에 대해서는 파고들지 못하는 것이 사실이다.

부족하게나마 필자의 글을 통해서 닷넷 CLR에 대한 이해를 높이고 양질의 제품을 생산할 수 있는 가능성을 높여주는 밑거름이 될 수 있었으면 한다.