memset 함수 주의점

C / C++ 2012.01.31 11:48 |

메모리를 다루는 다양한 함수가 있고, 이에 대한 사용법은 정확히 알고 있으면 매우 편리합니다.

배열, struct, class를 초기화할 때 memset을 이용하면 한 번에 초기화 할 수 있습니다.

이러한 memset 함수를 사용할 때 주의해야할 점들에 대해서 알아보고자 합니다.

1.memset을 사용할 때 주의할 점

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

- new, malloc 등을 이용하여 동적으로 배열을 생성하는 변수가 있는 

   struct, class에서는 memset으로 초기화를 하면 안됩니다..

- 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에서는 memset으로 초기화를 하면 안됩니다.

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

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을 위한 

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

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

이때

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은 매우 편리하고, 강력하면서도, 조심해서 사용해야 한다는 것을 명심하세요.

'C / C++' 카테고리의 다른 글

C++ Template 기초와 Template Meta Programming  (0) 2012.10.19
정수를 문자열로 변환하는 함수  (0) 2012.10.19
부분문자열을 검색하는 함수  (0) 2012.09.08
Vector 사용시 주의점  (0) 2012.01.31
Memory Leak 탐지 방법  (1) 2012.01.31
memset 함수 주의점  (3) 2012.01.31
Posted by 엘비뉴

댓글을 달아 주세요

  1. 오뇽 2013.11.27 10:39 신고 Address Modify/Delete Reply

    안녕하세요.
    이 게시글 내용이 좋아 제 블로그에 출처 밝히고 퍼갔습니다.
    좋은 내용 감사합니다 ^^

    http://shaeod.tistory.com/282

  2. 0cha 2015.04.29 02:55 신고 Address Modify/Delete Reply

    안녕하세요. 저도 출처밝히고 퍼갑니다.
    좋은글 감사합니다.

  3. Ho.# 2019.02.08 14:43 신고 Address Modify/Delete Reply

    저도 좋은글 퍼갑니다 출처 밝힐게용!!