[C#] 가비지 컬렉터


C++에서 클래스를 만드는 과정에 있어

가장 신경썻던 부분이 new/delete를 통한

메모리 할당/해제가 아니었나 생각이 든다.


할당은 그렇다 쳐도 해제하는 것을 까먹는다거나

해제한 줄도 모르고 그 포인터에 접근하는 경우가 발생한다.

때문에 스마트 포인터를 사용하여 이 문제를 극복하곤 하는데

c#에서는 가비지 컬렉터라는 멋진 놈이 이러한 문제를 해결해준다.




C/C++의 메모리 할당

C/C++는 힙에 객체를 할당하기 위해 C-런타임은 객체를

담기 위한 메모리를 여러 개의 블록으로 나눈 뒤, 이 블록을

연결 리스트로 묶어서 관리하게 된다.


어떤 객체를 힙에 할당하는 코드가 실행되면, C-런타임은

메모리 연결 리스트를 순차적으로 탐색하면서 해당 객체를

담을 수 있을 만한 여유가 있는 메모리 블럭을 찾는다.




적절한 크기의 메모리 블록을 만나면, 프로그램은 이 메모리

블록을 쪼개서 객체를 할당하고 메모리 블록의 연결리스트를

재조정한다. 따라서 메모리 공간에 데이터를 집어넣는다는 것은

1. 탐색 2. 분할 3. 재조정의 오버헤드가 필요하다는 것이다.




C#의 메모리 할당

C#은 CLR이 자동 메모리 관리 기능을 제공한다.

이 기능의 중심에는 가비지 컬렉션 (Garbage Collection) 이 있다.

가비지 컬렉션은 프로그래머로 하여금 컴퓨터가 무한한 메모리를

가지고 있는 것처럼 간주하고 코드를 작성할 수 있게 한다.

(여기서 가비지란 더 이상 사용하지 않는 객체)




CLR 안에는 이러한 가비지 컬렉션을 담당하는

가비지 컬렉터 (Garbage Collector)라는 놈이 있다.

가비지 컬렉터는 객체 중에 쓰레기인 것과 쓰레기가

아닌 것을 완벽하게 분리해서 쓰레기들만 수거한다.




하지만 가비지 컬렉터 역시 CPU와 메모리 자원을 소모한다.

때문에 가비지 컬렉터가 최소환으로 이 자원을 사용할 수 있게

만들 수 있다면 성능을 아낀 자원의 양만큼 끌어올릴 수 있게 된다.




그렇다면 가비지 컬렉터가 최소환으로 자원을 사용하게 만들기 위해

우선 가비지 컬렉터가 어떻게 동작하는지에 대한 메커니즘을 이해해야 한다.

포스팅이 길어질테니 여기서 끊고, 다음 포스팅에서 가비지 컬렉터의

동작 메커니즘에 대해 알아보도록 하겠다.





[자료구조] 연결 리스트의 이해(2)


오늘은 연결 리스트의 초기화, 삽입, 조회, 삭제가 어떻게

동작하는 지에 대해 알아보고 가는 시간을 갖도록 하겠다.


자료구조란 학문은 그림을 그리면서 이해하는 것이 가장 중요하다.

아래의 포스팅은 코드만 나와있지만, 공부를 제대로 하고자 한다면

자신이 직접 그림을 그리면서 이해를 해야한다.




01. 초기화




02. 삽입




03. 조회




04. 삭제




연결 리스트가 어떻게 동작하는지 간단한 코드 예시를 보았다.

앞서 말했듯이 자료구조라는 학문은 그림을 그리면서 이해하는

것이 무엇보다 중요하다. 그림도 같이 올려서 포스팅 하고 싶지만

많은 시간이 걸리기 때문에 소스 코드만 포스팅하게 되었다.




위의 소스 코드를 보면 연결 리스트의 삽입, 조회, 삭제의 과정에서

첫 노드와 첫 노드 이외의 모든 노드의 처리과정이 분리되어

있다는 것을 알 수 있다. 이 보다 좀 더 좋은 코드가 되기 위해서는

모든 처리가 while(1){ .. } 안에 들어가야 할 것이다.




하지만 이는 좀 더 복잡한 코드가 나오게 할 뿐이다.

이에 대한 해결책은 더미 노드라는 것인데,

이렇게 첫 노드와 나머지 노드를 나누어 처리하는 이유와

더미 노드란 무엇이며 어떻게 적용되는 지에 대해서는

다음 포스팅에서 계속 이어 가도록 하겠다.





public 상속은 반드시 is-a 관계를 따르도록 하자.


사람 <------ 학생

위의 표현은 C++ 기본서에서

public 상속을 설명할 때 가장

빈번히 활용되는 예이다.




학생은 사람이라서 사람이 할 수 있는

(먹기, 앉기, 눕기 등등)을 할 수 있지만

모든 사람은 학생이 아니기 때문에

모든 사람은 학교를 다닌다가 참이 될 수 없다.




즉, Base클래스 (사람) 은 일반적인 개념을 타나내며

Drived클래스 (학생) 은 좀 더 특수화된 개념을 나타낸다.


public 상속과 is-a 관계가 똑같은 뜻이라는 이야기는

꽤 직관적이고 간단하긴 하지만, 그 직관 때문에 잘못된

판단을 하는 경우가 있다. 아래의 예제를 보도록 하자.

펭귄은 새의 일종이고, 새는 날 수있다는 것을 알고있다.




class Bird{

public:

virtual void fly();            // 새는 날 수 있다.

...

};

class Penguin: public Bird{    // 펭귄은 새이다.

...

};


새는 날 수 있다는 사실을 바탕으로 클래스를 디자인 했는데

펭귄이라는 새는 날지 못한다. 모든 새는 날지 못한다는 점도 

구분하여 좀 더 현실에 가까운 클래스 계통구조를 만들어야 한다.




class Bird {

...                                            // fly 함수가 없다.

}


class FlyingBird : public Bird{

public:

virtual void fly();

...

};


class Penguin: public Bird {

...                                            // fly 함수가 없다.

};




좀 더 현실에 가까운 클래스 구조가 되었다.

하지만 어떠한 소프트웨어 시스템이냐에 따라

새의 비행능력을 고려하지 않아도 되는 경우가 있다.

만약 새의 서식지와 먹이에 대한 응용프로그램이라면

처음 디자인한 클래스가 더욱 만족스러울 것이다.

즉, 프로그램의 요구 사항에 맞게 디자인 하는 것이 중요하다는 것이다.




이번에는 위의 펭귄문제를 런타임 에러를 통해 해결해보자.


void error(const std::string& msg);


class Penguin : public Bird {

public:

virtual void fly() { error ("Attempt to make a penguin fly!"); }

...

};


이 경우는 "펭귄은 날 수 없다." 가 아닌

"펭귄은 날 수 있다. 하지만 날려고 하면 에러가 난다" 이다.

"펭귄은 날 수 없다" 라는 것은 컴파일러가 판단할 수 있지만,

"날려고 하면 에러가 난다" 는 런타임에만 발견할 수 있다.




자 그럼 이번에는 컴파일 타임에서 이 문제를 해결해 보도록 하자.


class Bird{

...                                    // fly 없음

};


class Penguin : public Bird {

...                                    // fly 없음

};


Penguin p;

p.fly();                                    // 에러


이제 펭귄을 날려 보려고 하면, 컴파일 단계에서 문제가 생기게 된다.

런타임 에러를 내주는 것은 p.fly를 호출하는 것에 대해 문제가 없다.

런타임, 컴파일 타임 과연 어떤 해결책이 더 좋은 것일까?




유효하지 않은 코드를 컴파일 단게에서 막아 주는 인터페이스가

좋은 인터페이스이다. 즉 , 펭귄의 비행을 컴파일 타임에 거부하는

설계가 그것을 런타임에 뒤늦게 알아내는 것보다 훨씬 좋다는 것이다.




새와 펭귄 문제는 이쯤하고, 사각형과 정사각형을 클래스의

상속 계통으로 만드는 문제로 넘어가 보자. 초등학교 수학시간에

배운 기억을 바탕으로 우리는 "정사각형은 직사각형이다."라는

사실을 알고 있을 것이다. 때문에 정사각형은 직사각형을

상속받도록 디자인 해야한다고 생각하는 것도 당연할 것이다.




하지만 직사각형은 넓이와 높이 모두 자유자재로 바뀌어도

직사각형이라는 것을 만족한다. 하지만 이를 상속받는

정사각형은 가로 새로가 항상 일치해야 한다는 조건이 있다.

때문에 직사각형에 가로 혹은 세로를 마음대로 조정하는

함수가 있다면, 정사각형에는 적용할 수 없는 함수이다.

때문에 이 케이스틑 is-a관계가 적용될 수가 없다.




여기서 중요한 부분은 public 상속은 기본 클래스 객체가

가진 모든 것들이 파생 클래스 객체에도 그대로 적용되는 상속이다.

때문에 직사각형과 정사각형은  public 상속으로 표현한다면

틀린 것이고, 컴파일 수준에서 무사히 통과과 되었다 해도

제대로 동작할 것이라는 보장은 없다.




코드가 컴파일된다는 것이 제대로 동작한다는 의미는 아니다.

이런 점 때문에 프로그래머는 계속 배움의 길을 개척해 가야한다.


Point!!

public 상속의 의미는 is-a 이다.

기본 클래스에 적용되는 모든 것들이

파생 클래스에 그대로 적용되어야 한다.

모든 파생 클래스 객체는 기본 클래스 객체의 일종이기 때문이다.




+ Recent posts