반응형
Buffer Manipulation을 지원하는 다양한 함수가 있고, 이에 대한 사용법은 정확히 알고 있으면 매우 편리하다. 지난시간에 설명한 memset에 대한 추가적인 설명으로 오늘 마무리 하고자 한다.

배열, struct, class를 초기화할 때 memset을 이용하면 한 번에 초기화 할 수 있음을 지난시간에 배웠다. 이번시간에는 memset을 사용할 때 매우 주의해야할 점에 대해서 알아보자.

1.memset을 사용할 때 주의할 점
- 1Bytes 변수(char, unsigned char 등)를 제외한 변수를 초기화 할 때에는 0이외의 값으로 초기화를 절대 하지마라.
- new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 초기화할 때 조심해라.
- CString은 절대 memset으로 초기화를 하지마라.
- virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하지마라.

memset을 사용할 때 위 4가지 경우만 기억을 하고 있으면 문제없다. 반드시 기억하고 있어야 한다. 각각에 대해서 간단하게 살펴보도록 하자.

1.1 1Bytes 변수(char, unsigned char 등)를 제외한 변수를 초기화 할 때에는 0이외의 값으로 초기화를 절대 하지마라.

이는 지난 시간에 설명을 한 부분이다. 다시 살펴보면,
int n;
memset(&n, 1, sizeof(int));
으로 하면 Byte단위로 처리가 되어 n = [00000001000000010000000100000001] = 16843009의 원하지 않는 값으로 초기화가 된다는 것을 명심해두자. 1Byte의 변수를 제외하고는 0으로만 초기화를 하는데 이용하자.

1.2 new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 초기화할 때 조심해라.

문제가 되는 경우를 살펴보면,
struct A {
   int i;
   char* c;
};

void main()
{
   A a;
   a.c = new char[100];
   memset(&a, 0, sizeof(A));
   if(a.c != NULL) {
      delete[] a.c;
      a.c = NULL;
   }
}

여기서 sizeof(A)는 struct member alignment가 어떤 값이든 4(int i) + 4(char* c, address는 4) = 8Bytes가 된다. 그러므로 위의 소스는 동적으로 생성한 변수는 초기화가 되지 못하고, char* c가 NULL로 초기화가 됨으로써, 이전에 생성한 메모리는 메모리 누수가 발생하게 된다.

그러므로 위와 같이 동적으로 생성하는 경우 각각을 분리하여 초기화를 하여야겠다.
a.i = 0;
memset(a.c, 0, sizeof(char)*100);

1.3 CString은 절대 memset으로 초기화를 하지마라.

1.2와 같은 경우로 CString은 내부적으로 m_pchData 변수를 동적으로 생성하여 문자열을 저장한다. 이 변수에 대한 직접적인 접근은 private로 막혀있다. 그래서 CString, 또는 CString을 member variable을 가지고 있는 struct, class를 memset을 이용하여 초기화를 하지마라.

CString을 memset으로 초기화를 하면, 1.2와 같이 메모리 누수뿐만 아니라, run-time error도 발생을 한다. 나를 포함해서 이것 때문에 고생을 한 사람이 꽤 많다는 것을 기억해두자.

1.4 virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하지마라.

여기서 virtual은 run-time에 실행함수를 binding하는 역할을 하는 것이다.
한번 잊어버렸던 기억을 되살리는 의미로 예제를 살펴보면,

class A {
public:
   void fun() { printf("A::fun() "); }
};

class B: public A {
public:
   void fun() { printf("B::fun() "); }
};

void main()
{
   A* a = new B();
   a->fun();
}

위의 경우 “A::fun()”이 출력된다.
하지만 상속을 하여 재정의를 한다는 목적은 재정의를 한 함수가 호출되기를 바라기 때문일 것이다. 이때 아래와 같이 class A만 간단히 변경하여 virtual만 추가를 하면,
class A {
public:
   virtual void fun() { printf("A::fun() "); }
};

재정의한 함수가 실행이 되어 “B::fun()”이 출력이 된다. 이는 a->fun();가 실행이 될 때, 이 함수가 virtual이므로 a의 실제 instance(=new B)에 대응하는 실제 함수를 run-time으로 binding 되기 때문이다.

이정도로만 virtual 동작에 대해서 기억을 되살려보는 것으로 마무리를 하고, 다시 memset으로 넘어오면,

class A {
   int i;
   char* c;
   void fun();
};
을 sizeof(A)를 하면 4(int i)+4(char*c, address)=8Bytes로 member function은 영향을 주지 않는다. 하지만,
class A {
   int i;
   char* c;
   virtual void fun();
};
의 경우는 다르다. 위의 8Bytes외에 실제 fun()이 binding을 위한 실행함수 주소를 저장할 공간 4Bytes를 추가적으로 가지므로, sizeof(A)는 총 12Bytes가 된다.

이때
class A {
public:
   void fun() { printf("A::fun() "); }
};

class B: public A {
public:
   void fun() { printf("B::fun() "); }
};

void main()
{
   A* a = new B();
   memset(a, 0, sizeof(B));
   a->fun();
}
와 같이 memset을 이용하여 초기화를 하면, virtual function이 NULL영역으로 binding이 되어, a->fun();에서 run-time에 알 수 없는 에러가 발생한다는 것을 반드시 기억해두자.

memset은 매우 편리하고, 강력하면서도, 조심해서 사용해야 한다는 것을 기억해두자.
반응형

'프로그래밍 > C 언어' 카테고리의 다른 글

c++ 에서 new, delete 메모리 할당 해제 관련글  (0) 2009.05.07
debuging 하기. (아파치에서)  (0) 2009.02.11
c언어 함수 요약  (0) 2009.02.11
날짜 구하기  (0) 2009.02.11

+ Recent posts