출처 : 잡동사니님 블로그


- 1Bytes 변수( char, unsigned char 등 )를 제외한 변수를 초기화 할 때는 0 이외의 값으로 초기화를 하면 안됨

- new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 memset으로 초기화를 하면 안됨

- CString은 절대 memset으로 초기화를 하면 안됨

- virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하면 안됨


memset을 사용할 때 위 4가지 경우만 기억을 하고 있으면 문제없이 동작합니다. 

각각에 대해서 간단하게 살펴보도록 하죠


-1Bytes 변수( char, unsigned char 등 )를 제외한 변수를 초기화 할 때는 0 이외의 값으로 초기화를 하면 안됩니다.

1
2
int n;
memset(&n, 1, sizeof(int));

으로 하면 Byte단위로 처리가 되어 

n = [00000001000000010000000100000001] = 16843009의 원하지 않는 값으로 초기화가 되버립니다. 

따라서 1Byte의 변수를 제외하고는 0으로만 초기화를 하는데 이용해야 합니다.


-new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 struct, class에서는 memset으로 초기화를 하면 안됩니다

문제가 되는 경우를 살펴보면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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로 초기화가 됨으로써, 

이전에 생성한 메모리는 메모리 누수가 발생하게 됩니다.

그러므로 위와 같이 동적으로 생성하는 경우는 아래와 같이 각각을 분리하여 초기화를 하여야 합니다.

1
2
a.i = 0;
memset(a.c, 0, sizeof(char)*100);


-CString은 절대 memset으로 초기화를 하면 안됩니다.

1.2와 같은 경우로 CString은 내부적으로 m_pchData 변수를 동적으로 생성하여 문자열을 저장합니다. 

이 변수에 대한 직접적인 접근은 private로 막혀 있습니다.

그래서 CString, 또는 CString을 member variable을 가지고 있는 

struct, class를 memset을 이용하여 초기화를 하면 안됩니다.

CString을 memset으로 초기화를 하면, 1.2와 같이 메모리 누수뿐만 아니라, run-time error도 발생을 합니다.


-virtual function을 가지고 있는 struct, class에서는 절대 memset으로 초기화를 하면 안됩니다.

여기서 virtual은 run-time에 실행함수를 binding하는 역할을 하는 것입니다.

한번 잊어버렸던 기억을 되살리는 의미로 예제를 살펴보면,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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만 추가를 하면,

1
2
3
4
5
class A
{
public:
   virtual void fun() { printf("A::fun() "); }
};

재정의한 함수가 실행이 되어 “B::fun()”이 출력됩니다. 이는 a→fun();가 실행이 될 때,

이 함수가 virtual이므로 a의 실제 instance(=new B)에 대응하는 실제 함수를 run-time으로 binding 되기 때문입니다.

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

1
2
3
4
5
6
class A
{
   int i;
   char* c;
   void fun();
};

을 sizeof(A)를 하면 4(int i)+4(char*c, address)=8Bytes로 member function은 영향을 주지 않습니다. 

하지만,

1
2
3
4
5
6
class A
{
   int i;
   char* c;
   virtual void fun();
};

의 경우는 다릅니다. 

위의 8Bytes외에 실제 fun()이 binding을 위한 

실행함수 주소를 저장할 공간을 가리키는(VPTR) 4Bytes를 추가적으로 가지므로, 

sizeof(A)는 총 12Bytes가 됩니다.

이때

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class A
{
public:
   virtual 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은 매우 편리하고, 강력하면서도, 조심해서 사용해야 한다는 것을 명심하세요.

+ Recent posts