728x90

멤버 이니셜라이져(Member Initializer) 

 

말 그대로 클래스의 멤버를 초기화 하는 것으로

 

클래스

{

     멤버변수 1, 멤버변수 2;

}

클래스명(타입1 변수1, 타입2 변수2) : 멤버변수1(변수1), 멤버변수2(변수2){} 방식이다.

 

ex)

class Student

{

     int id;

     float num;

}

 

Student(int _id = 0, float ft_num = 0.0f) : id(_id), num(ft_num){}

 

이 문장들은 멤버변수 id와 num을 변수 _id 와 ft_num으로 초기화 하라는 말이다.

멤버 이니셜라이져는 생성자의 몸체부분보다 먼저 실행되는 특징이 있기 때문에 상속을 받은 클래스에서 부모 클래스의 변수를 초기화 해야 할 필요가 있을 경우 사용한다.

또한 const 멤버 변수를 초기화 할 수 있는 특징도 가지고 있기에 유용하게 쓰이는 문법이다.

그래서 const 멤버 변수는 반드시 이니셜 라이져를 이용해서 초기화 한다.

 

effective c++에서는 클래스의 변수를 초기화할 때에는 생성자의 함수 몸체에서 하지말고 멤버 이니셜라이져를 사용해라고 한다. 이유는 생성자의 멤버 이니셜라이져는 초기화이지만, 생성자의 함수 몸체에서 한다면 그것은 그냥 대입이라고 한다.

 

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

Zeromemory(&구조체, sizeof(구조체)); 

memset(&구조체, 0, sizeof(구조체));

 

이 두개는 컴파일 최적화 옵션을 해제하고 디스 어셈블 해보면

004017F0     push     24h

004017F2     push     0

004017F4     lea        eax, [ebp-70h]

004017F7     push     eax

004017F8     call       memset(401000h)

004017FD    add       esp, 0CH

 

결과 값과 컴파일 결과가 일치하는 것을 알 수 있다.

인자를 스택에 넣고, memset의 주소를 콜하는 방식이다.

실제적으로 두개의 차이는 없다고 할 수 있지만 Zeromemory는 0으로만 채워줄 뿐 memset과 같이 원하는 값을 초기화 값으로 줄 수 없다.

단지 Zeromemory는 0x00으로 해주는 수고를 줄여줄 뿐이다.

가독성면으로 보더라도 Zeromemory가 0으로 초기화 하겠다는 것을 쉽게 알 수 있으므로 별 차이가 없다면 Zeromemory를 쓰도록 하자.

 

또 다른 구조체 초기화 방법은

구조체타입 객체변수 = {0};

ex) Student S1 = {0};

 

코드를 디스 어셈블 해보면 결과는

00401800     mov     dword ptr[ebp-4Ch], 0

00401807     xor      ecx, ecx

00401809     mov     dword ptr[ebp-48h], ecx

0040180C    mov     dword ptr[ebp-44h], ecx

0040180F    mov     dword ptr[ebp-40h], ecx

 

2번째 줄에서 볼 수 있듯이 xor, ecx, ecx 명령어로 단순히 그 값을 0으로 채워주는 것 밖에 없다.

한가지 알아 둘 것은 구조체의 크기가 커지면 컴파일 시 자동으로 memset으로 변환한다는 것이다. 

 

즉  3개의 차이는 그다지 크지 않으므로 자신이 보기 편하고 가독성이 좋다고 생각하는 것을 쓰도록 하자.

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

volatile 키워드 

 

컴파일러의 최적화 옵션의 적용을 받지 않도록 하는 선언자.

비슷한 것으로 constant 선언이 있는데 서로 다른 점이라면  constant 선언의 경우에는 변수 정의 시점에 대입된 값이 어떠한 경우에도 변경되지 않도록 보호하는 형태 즉 상수의 형태로 보존되도록 하라는 옵션이다.

 

이와 달리 volatile의 경우에는 컴파일러가 프로그램의 코딩을 최적화를 통해 임의로 수정하지 못하도록 하는 명령어로 변수 선언시 임의로 레지스터에 변수를 올리지 않게 하고 메모리에 올리게 하며 코드는 임의로 수정을 하지 못하게 한다.

 

즉, 해당 변수의 데이터가 변할 수 있기 때문에 항상 해당 메모리에 접근해서 데이터를 직접 참조하라는 지시자이다.

 

간단한 예)

int i, a, b;

 

a = 0; b = 0;

 

for(i = 0; i < 10; i++)

     b += a * 100;      

 

이런 코드를 작성한 경우.. 일반적인 컴파일러로 최적화를 하게 되면

 

int i, a, b;

 

a = 0; b = 0;

 

for(i = 0; i < 10; i++)

     b += 0;       // 연산을 하지 않게 컴파일러가 알아서 보정해 버린다.

                         (최적화 옵션에 따라 다양하게 변할 수 있다.)

 

이처럼 컴파일러 차원에서 옵티마이저시 위와 비슷한 로직으로 자동 변환이 되게 되는데(정확히는 어셈블 과정에서 코드가 변환되어 비 효율적이라 판단되는 부분을 수정 또는 제거해 버린다.)

프로그램에 따라서는 이렇게 코드가 변경되면 안되는 경우가 있다.

즉 컴파일을 할 때 최적화 옵션으로 인해 변경될 경우를 방지하기 위한, 반드시 내가 명시한대로 실행할 필요가 있을 경우 사용하는 키워드이다.

 

또한 변수에 값을 대입 할 때 (메모리에 write) 항상 공유 메모리에 직접 쓰거나 읽지 않고 쓰기 버퍼(저장 버퍼)에 값을 임시로 저장했다가 실제 공유메모리에 write할 수 있다. 이럴 경우 멀티 스레드 환경에서 메모리를 읽었을 때 기대하는 값이 나오지 않을 수 있다.

 

 

register 키워드

 

해당 변수를 메모리에 저장하는 것이 아닌 실제로 연산이 일어나는 레지스터에 저장되게 한다.

레지스터는 고속 연산이 가능하므로 상당한 실행 시간을 보장 받을 수 있지만

용량의 한계가 있으므로 간단한 프로그램 위주로 많이 쓰인다.

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

1. static_cast 

묵시적 캐스르로 변수형을 변환해준다.

 

간단한 예)

int d = 65;

char ch = d;                              // 컴파일러가 묵시적으로 캐스팅을 해줌

char ch = static_cast<char>(d);  // 명시적으로 캐스팅을 해줌

  

묵시적 캐스팅과의 차이점

 - 클래스 포인터에 대해 묵시적 캐스트는 is-a관계가 성립하는 경우만 허용하고 static_cast는 is-a관

   계 뿐만 아니라 상속 관계일때도 캐스팅을 해준다.

 

Truck* pTruck = new Car;

Truck* pTruck = static_cast<Truck*>(new Car);

명시적 변환이기는 하나 컴파일 타임에 타입 체크를 하는 등 대체적으로 안전한 형 변환이다.

 

 

2. reinterpret_cast

일반적으로 허용하지 않는 위험한 형변환을 할 때 사용한다.

단지 안에 있는 비트열만 보고 원하는 형으로 강제 변환을 할 때 사용한다.

포인터를 정수로 변환하는 작업을 사용할 때 많이 사용한다.

 

간단한 예)

int a, b;

a = reinterpret_cast<int>(&b);

 

간단한 예2)

class A

{

private :

     char a;

     char b;

}

위 클래스 변수 b에 억지로 값을 기록하고자 한다면 다음과 같이 사용하면 된다.

A a;

char* p = reinterpret_cast<char*>(&a);

*(p+1) = 5;

 

 

3. dynamic_cast

유일하게 c스타일의 형변환으로 불가능한 캐스팅.

상속관계에 있는 클래스의 형변환을 수행하며, 동시에 안전한지 검사 한다.

 

간단한 예)

Truck* pTruck = new Car;

Truck* pTruck = dynamic_cast<Truck*>(new Car);

 

 

4. const_cast

const 속성이나 volatile속성을 해제 할 때 사용한다.

변수를 정의할 때 volatile 키워드를 사용한다.

즉 상수로 정의한 변수를 변경하고 싶을 때 사용한다.

 

간단한 예)

RECT rectA;

CONST RECT rectB;

 

rectA = const_cast<RECT>(rectB);

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

템플릿은 C++을 강력한 언어로 만들어주는 기법 중의 하나입니다.

 

템플릿이라는 문법을 통해서 편리하게 STL을 사용할 수도 있습니다.

 

이 중에 auto_ptr이라는 스마트 포인터가 있습니다. 

 

auto_ptr은 <memory>에 구현되어 있습니다. 

 

제목에서 보이듯이 이 스마트 포인터는 반만 스마트합니다.

 

그 이유를 차근차근 보도록 하겠습니다.

 

auto_ptr은 템플릿 클래스로 만들어져 있기 때문에,

어떤 타입의 포인터든 받을 수 있습니다.

 

클래스의 특징 중 하나는 Scope(유효 범위)를 벗어나면 소멸자가 호출된다는 것인데

 

이 특징을 이용하고 있습니다.

 

만약 다음과 같은 코드가 있다면 메모리의 누수가 생깁니다.

 

void main()

{

        int* pInt = new int;

        *pInt = 10;

        cout<<*pInt<<endl;

        // delete pInt;

}

 

new로 동적할당을 하고 delete를 호출하지 않아서 메모리 누수가 있습니다.

 

void main()

{

        auto_ptr<int> pInt(new int);

        *pInt = 10;

        cout<<*pInt<<endl;

}

 

이렇게 auto_ptr을 사용 가능합니다.

 

<> 안에 사용할 타입을 넣어주시면 됩니다.

 

auto_ptr은 구현을 보시면 소멸자(~auto_ptr())에 다음과 같이 되어 있습니다.

 

~auto_ptr()

{

        delete _Myptr;
}

 

_Myptr은 auto_ptr이 내부적으로 관리하는 포인터입니다.

 

생성자가 동적할당된 주소를 받게 되어 있는데, 그 주소를 가리키는 포인터입니다.

 

Visual Studio에서는 _Ty *_Myptr; 으로 되어 있습니다.

 

템플릿에서 흔히 쓰는 표현으로는 T_Myptr이 됩니다.

 

넘겨받은 주소를 저장하고 갖고 있다가 소멸자가 호출될 때,

 

delete _Myptr; 을 호출하는 것 뿐입니다.

 

여기서 auto_ptr의 한 가지 단점이 나옵니다.

 

동적할당을 배열 단위로 하면 정상적으로 메모리 해제가 안된다.

 

new int [5];로 할당하면 delete[] _Myptr로 바뀌어야 하는데

 

이미 소멸자의 코드는 delete _Myptr;로 고정되어 있습니다.

 

이 경우에는 메모리의 누수를 가져올 수 있습니다.

 

그리고 malloc()으로 할당된 메모리에 대해서도 정상적으로 해제가 안될 수 있습니다.

 

같은 이유로 malloc()은 free()와 쌍을 이뤄야 하기 때문입니다. 

 

이러한 문제를 다른 스마트 포인터는 functor(함수 객체)라는 

 

문법을 사용하여, 회피하고 있습니다. 

 

소멸자에서 호출할 메모리 해제 코드를 직접 지정할 수 있습니다. 

 

그리고 대입 등으로 내부의 포인터를 복사하면 복사가 되는 것이 아니라

 

이전의 auto_ptr은 NULL로 무효화처리 됩니다.

 

a = b;

 

위와 같을 때 일반적으로는 a와 b값이 같아지지만,

 

auto_ptr은 a에 b의 값이 들어가면서 b는 NULL로 바뀌어버립니다.

 

이렇게 되는 이유는 만약 a와 b가 같은 위치를 가리키고 있을 때, a가 먼저 delete로

 

해당 메모리 영역을 해제해버리면 b는 이미 해제된 영역을 다시 delete하기 때문에

 

문제가 생기기 때문입니다.

 

여기서 auto_ptr의 두 번째 단점이 나옵니다.

 

한 곳의 위치를 가리키는 2개의 auto_ptr을 생성할 수 없다.

 

억지로 가리킬 수는 있어도 나중에 해제 시에 반드시 문제가 생기게 됩니다.

 

auto_ptr에 release()라는 메소드가 존재하는데

 

자기가 갖고 있는 포인터를 반납(리턴)하면서 포인터를 NULL로 바꿉니다.

 

그래서 release()를 사용하면, 포인터를 돌려받고, auto_ptr 객체가 무효화 됩니다.

 

그래서 스마트 포인터들은 이 문제를 참조 카운트(Reference Count)를 통해서

 

해결합니다.

 

참조 카운트를 유지하면서 대입을 하거나 했을 때, 카운트를 하나씩 올려주고

 

소멸자가 호출되면, 카운트를 다시 하나씩 내려줍니다.

 

그러다가 마지막에 카운트가 0이 되는 순간 메모리를 해제하는 방법입니다.

 

shared_ptr이 이런 스마트 포인터 중 하나이며  

 

COM에도 이런 기능을 하는 스마트 포인터들이 존재합니다.(CComPtr 등등...) 

또, C++11에서는 auto_ptr에 Deprecated 되었다고 나옵니다.

 

앞으로를 위해서는 auto_ptr은 절대 사용하면 안됩니다.

 

unique_ptr은 auto_ptr을 대체합니다.  

그 외에도 대체할 수 있는 스마트 포인터가 많이 있으므로 다른 스마트 포인터를

 

사용하시면 됩니다.


출처 : http://psychoria.blog.me/40155382971

728x90
728x90

- explict - 

explicit는 명시적 호출만 허용하고 묵시적 호출은 허용하지 않는다.

쓰임은 아주 간단하기 때문에 한개의 예로 간단히 알 수 있다.


- 사용 예 -

class AA

{

private : 

int v;


public :

explicit AA(int _v)    // explicit이 적용된 생성자는 묵시적 형변환을 허용하지 않는다.

{

v = _v;

cout << "값 : " << v << endl;

}

};


- mutable -

mutable은 const명령어를 사용하여 상수화시킨 변수에 대해 예외를 두고 변수를 바꾸지 못하는 제한을 풀어버리는 키워드이다. c++의 유연성을 위해 제공하고 있는데 많은 프로그래머들이 mutable을 쓰는 것을 꺼려한다고 한다. 유연성을 위한다고 존재하지만 프로그램의 혼란을 초래할 수 있기 때문이다.

 

-- mutable이 적용된 예 --

class MyClass

{

     private :

          mutable int x;     // Mutable 선언

          int y;

 

     public :

          void SetData(int a, int b) const

          {

               x = a;     // x가 mutable이므로 변경 가능.

               y = b;     // 에러

          }

};

 

int main()

{

     MyClass a;

     a.SetData(10, 20);

     return 0;

}

 

출처 : http://blog.naver.com/sorkelf/40146367692

728x90
728x90

# 객체 포인터 변수

객체의 주소값을 저장하는 포인터 변수

Person * ptr; // 객체 포인터 변수 선언
ptr = new Person(); // 포인터 변수의 객체 참조

위 코드에서 ptr은 Person객체를 가리키게 된다. 그런데 Person형 포인터는 Person 객체뿐만 아니라, 
Person을 상속하는 유도클래스의 객체도 가리킬 수 있다.

class Student : public Person { ... }

Person *ptr = new Student(); // Person을 상속하는 Student 객체를 가리킨다.

class PartTimeStudent : public Student { ... }

Student *ptr = new PartTimeStudent; // Student를 상속하는 PartTimeStudent 객체를 가리킨다.

Person *ptr = new PartTimeStudent; // 기초클래스를 간접적으로 상속하는 PartTimeStudent도 가리킨다.

이를 일반화하면 C++에서 객체 포인터 변수는 선언된 객체를 직접 혹은 간접적으로 상속하는 모든 객체를 
가리킬수 있다.


IS-A관계를 통해서도 논리적으로 이해가 가능하다.

- 학생은 사람이다. (사람 → 학생)
- 근로 학생은 학생이다. (학생 → 근로학생)
- 근로 학생은 사람이다. (사람 → 근로학생)

※ 사람은 학생을 가리킬수 있지만 학생은 사람을 가리킬수 없다. 이 또한 현실세계를 역행하는 코드가 된다.

■ 객체 포인터의 권한
- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- 포인터의 자료형(객체)에 따라 가리키는 대상에 관계없이 클래스에 정의된 멤버에만 접근가능하다.


# 함수 오버라이딩(function overriding)

함수 오버라이딩은 기초 클래스의 멤버함수와 같은 함수를 유도 클래스에 정의했을때 이를 가리켜 "유도클래스의
함수가 기초클래스의 함수를 오버라이딩 했다"라고 한다. 오버라이딩 된 기초 클래스의 함수는, 오버라이딩을 한
유도클래스의 함수에 가려진다.


※ 기초클래스와 동일한 이름의 함수를 유도클래스에서 정의한다고 해서 무조건 함수오버라이딩 되는것은 아니다.
매개변수의 자료형 및 갯수가 다르면, 이는 함수오버로딩 되어, 전달되는 인자에 따라서 호출되는 함수가 결정된다.
즉, 함수 오버로딩은 상속의 관계에서도 구성이 될 수 있다.


# 가상 함수(Virtual Function)

함수를 오버라이딩하는 이유는 객체를 가리키는 포인터 변수가 현재 자신이 가리키고 있는 객체의 멤버가 호출
되도록 하기 위함이다. 만약 포인터의 자료형을 이유로 자료형의 클래스의 멤버함수가 호출된다면 이건 분명 
잘못된것이다. 따라서 C++에서는 '가상함수'라는 것을 제공하고있다.

#include <iostream> 
usingnamespacestd;

class First
{
public:
virtualvoid MyFunc()
{
cout<<"FirstFunc"<<endl;
}
};

class Second: public First
{
public:
virtualvoid MyFunc()
{
cout<<"SecondFunc"<<endl;
}
};

class Third: public Second
{
public:
virtualvoid MyFunc()
{
cout<<"ThirdFunc"<<endl;
}
};

int main(void)
{
Third * tptr=new Third();
Second * sptr=tptr;
First * fptr=sptr;

fptr->MyFunc();
sptr->MyFunc();
tptr->MyFunc();
delete tptr;
return0;
}


※ 위 코드처럼 함수가 가상함수로 선언되면, 해당 함수호출시, 포인터의 자료형을 기반으로 호출대상을 결정하지
않고 포인터 변수가 실제로 가리키는 객체(Third)를 참조하여 호출의 대상을 결정한다.


■ 가상 함수의 실체

객체 안에는 실제로 멤버함수가 존재하지 않는다. 실제로 C++의 객체와 멤버함수는 함수를 공유하는 구조를 가진다.
객체가 생성되면 멤버변수는 객체 내에 존재하지만, 멤버함수는 메모리의 한 공간에 별도로 위치하고선, 이 함수가
정의된 클래스의 모든 객체가 이를 공유하는 형태를 취한다. 

virtual로 선언된 가상함수를 하나이상 포함하는 클래스에 대해서는 컴파일러가 '가상함수 테이블(Virtual-Table)'을
만든다. 이는 실제로 호출되어야 할 함수의 위치정보를 담고 있는데 이를 기반으로 함수가 호출되며 오버라이딩 된
가상함수는 유도클래스의 가상 함수 테이블에 존재하지 않는다. 때문에 가장 마지막에 오버라이딩 한 유도 클래스의
멤버함수가 호출된다.


■ 순수가상함수(Pure Virtual Function)와 추상클래스(Abstract Class)

class Employee 
{
private :
char name[100];
public:
Employee(char *name) {...}
void ShowName() {...}
virtualint GetPay() const = 0; // 순수 가상함수
virtualvoid ShowInfo() const =0; // 순수 가상함수
};


'순수가상함수'란 함수의 몸체가 정의되지 않은 함수를 의미한다.이를 표현하기 위해 코드처럼 '0의 대입'을 표시
하고 있다. 이것은 0을 대입하라는 것이 아니고 명시적으로 몸체를 정의하지 않았음을 컴파일러에게 알리는것이다.
이처럼 순수가상함수를 하나이상 지니는 클래스는 객체를 생성하려 할때 컴파일 에러가 발생한다.
이는 완전하지 않은, 그래서 객체생성이 불가능한 클래스라는 의미를 지니기 때문이다.

그리고 이러한 클래스를 가리켜 '추상클래스(abstract class)'라 한다



# 다형성(Polymorphism)

다형성은 어떤 문법이 아니라 가상함수의 호출관계에서 보인 특성을 가리켜 '다형성'이라고 한다.
'다형성(Polymorphism)'이란 '동질이상'을 의미한다. 즉, 다음과 같은 의미를 지닌다.

 "모습은 같은데 형태는 다르다"

이를 C++에 적용하면, "문장은 같은데 결과는 다르다"로 표현할 수 있다.

가장 대표적인 예로 자료형이 같은 포인터가 상속관계에 있는 서로 다른 객체를 가리키고 가상으로 선언된 함수를
호출하면 이는 다른 실행결과가 나타난다. 따라서 호출문장을 같지만 결과는 다른 '다형성'의 예로 볼 수 있다.


# 가상소멸자(Virtual Destructor)

virtual선언은 소멸자에도 올 수 있다. 객체의 소멸시 현재 참조하고 있는 포인터로 호출 할 경우 현재 참조되고 있는
객체의 소멸자만 호출된다. 따라서 이러한 경우에 메모리 누수가 발생하게 된다.
따라서 객체의 소멸은 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이 모든 소멸자가 호출되어야 한다.
그리고 이를 위해서는 소멸자에도 virtual 선언을 추가하면 된다.

virtual ~First()
{
   delete[]arr;
}

※ 가상 소멸자가 호출되면, 상속의 계층구조상 맨 아래에 존재하는 유도 클래스의 소멸자가 대신 호출되면서 기초 
클래스의 소멸자가 순차적으로 호출된다.



# 참조자의 참조 가능성

참조자(Reference)를 이용하여 객체를 참조할 경우 포인터의 특성 및 가상함수의 개념도 모두 그대로 적용된다.


출처 : http://lazypaul.tistory.com/9

728x90

'Basic Programming > C, C++' 카테고리의 다른 글

C++ - auto_ptr의 문제점  (0) 2016.02.26
C++ - Explict와 Mutable 키워드  (0) 2016.02.24
C++ - Visual C++에서 동적 메모리 체크하기  (0) 2016.02.24
C++ - 가변 인자  (0) 2016.02.24
C++ - GET_SET_ACCESSOR 매크로  (0) 2016.02.24
728x90


메모리를 동적으로 할당(new) 한 후 해제(delete) 되지 않았을때 Visual C++ 에서 알아내는 방법에 대하여 알아보자.

참고링크 [Bottom] [Top]

매크로 정의 [Bottom] [Top]

먼저 디버깅 모드에서 동적 메모리를 추적할 수 있도록 아래와 같이 매크로를 정의한다.

  • Toggle line numbers
       1 #ifdef _DEBUG
       2 #define new new( _CLIENT_BLOCK, __FILE__, __LINE__ )
       3 #endif
    

동적 메모리의 추적은 디버그 모드에서만 사용가능하다.

동적 메모리의 추적 방법 설정 [Bottom] [Top]

동적 메모리를 어떻게 추적할지를 제어하기 위하여 설정하는 것으로 메모리를 할당하기 전에 실행해야 한다.

  • _CrtSetDbgFlag( _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF );
    

동적 메모리의 추적 방법을 제어하는 비트 필드는 다음과 같다.

  • 비트 필드

    기본값

    설명

    _CRTDBG_ALLOC_MEM_DF

    설정

    디버그 할당을 사용한다. 이 비트를 해제하면 할당은 함께 연결되어 있지만 블록 형식은 _IGNORE_BLOCK 이다.

    _CRTDBG_DELAY_FREE_MEM_DF

    해제

    부족한 메모리 조건을 시뮬레이션하는 것과 관련해서 메모리가 실제로 해제되는 것을 방지한다. 이 비트를 설정하면, 해제된 블록이 디버그 힙의 연결 리스트에 보관되지만 _FREE_BLOCK으로 표시되고 특별한 바이트 값으로 채워진다.

    _CRTDBG_CHECK_ALWAYS_DF

    해제

    모든 할당 및 할당 취소에서 _CrtCheckMemory를 호출하게 한다. 실행 속도는 지연되지만 오류를 신속하게 찾아낸다.

    _CRTDBG_CHECK_CRT_DF

    해제

    _CRT_BLOCK 형식으로 표시된 블록을 누수 탐지와 상태 구별 작업에 포함시킨다. 비트를 해제하면 위와 같은 작업을 하는 동안 런타임 라이브러리가 내부적으로 사용하는 메모리를 무시한다.

    _CRTDBG_LEAK_CHECK_DF

    해제

    _CrtDumpMemoryLeaks를 호출하여 프로그램을 종료할 때 누수 검사를 수행한다. 응용 프로그램이 할당한 모든 메모리를 해제하는 데 실패하면 오류 보고서가 생성된다.

동적 메모리의 추적 예제 [Bottom] [Top]

  • Toggle line numbers
       1 #include <stdio.h>
       2 #include <string.h>
       3 #include <crtdbg.h>
       4 
       5 // 매크로 정의
       6 #ifdef _DEBUG
       7 #define new new( _CLIENT_BLOCK, __FILE__, __LINE__ )
       8 #endif
       9 
      10 int main()
      11 {
      12         // 동적 메모리의 추적 방법 설정
      13         _CrtSetDbgFlag( _CRTDBG_LEAK_CHECK_DF | _CRTDBG_ALLOC_MEM_DF );
      14 
      15         char* s1;
      16         char* s2;
      17 
      18         s1 = new char[128];     // 메모리 할당
      19         strcpy( s1, "string1" );
      20 
      21         s2 = new char[256];     // 메모리 할당
      22         strcpy( s2, "string2" );
      23 
      24         delete[] s1;            // 메모리 해제
      25 
      26         return 0;
      27 }
    

예제의 결과 [Bottom] [Top]

디버그 모드로 실행후 프로그램이 종료되면 아래의 Visual Studio 의 출력창에 다음과 같은 결과가 출력된다.

  • ----------------------------------------------------------------------------
    'debug.exe': 'C:\temp\debug\Debug\debug.exe' 로드, 기호가 로드되었습니다.
    'debug.exe': 'D:\WINXP\system32\ntdll.dll' 로드, 기호가 로드되지 않았습니다.
    'debug.exe': 'D:\WINXP\system32\kernel32.dll' 로드, 기호가 로드되지 않았습니다.
    Detected memory leaks!
    Dumping objects ->
    c:\temp\debug\main.cpp(21) : {44} client block at 0x003710B0, subtype 0, 256 bytes long.
    Data: <string2 > 73 74 72 69 6E 67 32 00 CD CD CD CD CD CD CD CD
    Object dump complete.
    '[3340] debug.exe: 기본' 프로그램이 0 (0x0) 코드에서 끝났습니다.
    ----------------------------------------------------------------------------
    

여기에서 아래의 메세지는 main.cpp 의 21 번째 줄에서 할당한 256 바이트가 해제 안된 것을 알려준다.

  • c:\temp\debug\main.cpp(21) : {44} client block at 0x003710B0, subtype 0, 256 bytes long.
    Data: <string2 > 73 74 72 69 6E 67 32 00 CD CD CD CD CD CD CD CD


출처 : http://www.viper.pe.kr/cgi-bin/moin.cgi/Visual_C%2B%2B_%EC%97%90%EC%84%9C_%EB%8F%99%EC%A0%81_%EB%A9%94%EB%AA%A8%EB%A6%AC_%EC%B2%B4%ED%81%AC%ED%95%98%EA%B8%B0#bottom



그럼 이제 간단히 첨부된 파일 C_Memory.h 파일을 보면서 메모리 릭 발생여부를 알아내고 이를 해결하는 방법에 대해서 알아보자. 

 

이는 MS 사에서 CRT(C Runtime) 라이브러리로 제공해는 CRTDBG 를 이용할 것이다.

 

일단 프로그램 정의부에 다음과 같이 추가한다. 

#include <crtdbg.h>

#define CRTDBG_MAP_ALLOC

#ifdef _DEBUG

#define new new( _NORMAL_BLOCK, __FILE__, __LINE__ ) //궁금하시면 제 게시글 중 <매크로 마법> 편을 보세요.

#endif

 

이제 메인 진입 부 처음에 다음과 같이 선언하고. 일반 main() 이라고 가정하자. 

void main() {

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

// .... 메모리를 동적 할당하는 무언가의 작업을 하겠죠

}

 

추가로 설명하자면, _CrtSetDbgFlag 설정 비트 필드 값은 다음과 같다. 

_CRTDBG_ALLOC_MEM_DF : 디버그 힙 메모리 할당을 허용하고, 또한 _CLIENT_BLOCK과 같은 사용자 정의 메모리 블럭을 사용한다.


_CRTDBG_LEAK_CHECK_DF : 프로그램 종료 시 자동으로 메모리 릭을 찾고 이를 출력하고, 이것은 _CrtDumpmemoryLeaks 함수를 호출한 것과 같은 결과이다. 

 

이제 프로그램을 디버그 모드로 실행하고 종료했을 때, 만약 동적 메모리 해제를 제대로 안해줬다면 다음과 같은 결과가 결과 화면에 출력될 것이다. 

 

 

  

우리에게 필요한 것은 바로 빨간색 선을 그은 정보이다. 우리는 이 번호를 이용해 메모리 릭이 일어나는 부분을 찾아갈 수 있다. 그러나 중요한 점은 똑같은 환경에서 다시 실행했을 때도 메모리 릭 현상 결과가 같아야 한다는 것이다. 그렇지 않으면, 매번 실행할 때마다 메모리 릭이 발생한 위치가 달라지고 따라서 위 번호도 달라져 버리기 때문이다. 따라서, 다른 작업을 하지 마시고 디버깅 작업만 하길 바란다.

일단 위 9637 번호를 이용해서 메모리 릭이 일어나는 곳을 찾아가보자. 이제 다시 main 첫부분에 다음과 같이 추가한다.

void main()

_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

_CrtSetBreakAlloc( 9637 );

// .... 메모리를 동적 할당하는 무언가의 작업을 한다...

}


그리고 다시 디버그 모드로 실행을 하면 9637에 지시하는 똑같은 메모리 번지를 알아서 브레이크 포인터를 걸어준다.

우리는 그 부분의 소스를 보고 콜 스택을 추적해 가면서 메모리 릭을 감지하고 해결할 수가 있다.

이런식으로 출력창에 출력되었던 다른 메모리 릭도 모두 찾아 해결하면 된다.

배포 전, 메모리 책임은 프로그래머의 미덕이지 않을까?


C_Memory.h


출처 : http://blog.naver.com/hermet?Redirect=Log&logNo=54353236


728x90

'Basic Programming > C, C++' 카테고리의 다른 글

C++ - Explict와 Mutable 키워드  (0) 2016.02.24
C++ - 함수 오버라이딩, 가상함수, 다형성  (0) 2016.02.24
C++ - 가변 인자  (0) 2016.02.24
C++ - GET_SET_ACCESSOR 매크로  (0) 2016.02.24
C++ - 헝가리안 표기법  (0) 2015.12.12

+ Recent posts