[완료] std::list 에서의 소멸자 호출??

bearchit의 이미지

일단 소스부터 보시죠...

#include <iostream>
#include <list>
 
using namespace std;
 
// std::list에 삽입하기 위한 클래스
class Test
{
public:
        // 생성자의 가장 중요한 임무는 int 타입의 동적메모리를 생성하는 것입니다.
        Test() {
                cid = id;
                cout << "[" << cid << "] Constructor called" << endl;
                id++;
                pa = new int;
        }
 
        // 소멸자는 생성자에서 생성된 int 타입의 메모리를 해제합니다.
        ~Test() {
                cout << "[" << cid << "] Destructor called" << endl;
                delete pa;
        }
 
protected:
        int *pa;             // int 타입의 동적메모리를 참조할 포인터
        static int id;       // 객체에 id를 할당하기 위한 카운터
        int cid;             // 생성된 객체의 id
 
public:
        int get_cid() const { return cid; }         // 객체 id 반환
};
 
int Test::id = 0;
 
typedef list<Test> TestList;
 
int main()
{
        TestList list;
        Test a, b, c;
 
        // std::list에 생성한 3개의 객체를 삽입합니다
        list.insert(list.end(), a);
        list.insert(list.end(), b);
        list.insert(list.end(), c);
 
        // std::list에 담긴 모든 객체의 id를 출력합니다
        TestList::iterator it;
 
        for(it=list.begin(); it!=list.end(); it++)
                cout << (*it).get_cid() << endl;
 
        // a, b, c 해제 및 list 해제
        return 0;
}

위의 소스에서처럼 내부에서 동적메모리 할당을 사용하는 클래스 Test를 만들었습니다.
Test의 객체들을 std::list에 삽입하려고 하는데, std::insert() 호출시,
위치를 리스트의 마지막(std::list.end())으로 지정하고 삽입하면,
list 해제시 오류가 발생합니다.

실행결과

[0] Constructor called
[1] Constructor called
[2] Constructor called
0
1
2
[2] Destructor called
[1] Destructor called
[0] Destructor called
[0] Destructor called
*** glibc detected *** ./dest: double free or corruption (fasttop): 0x0804b008 ***

실행결과를 보시면 아시겠지만, 프로그램이 종료될 때 list가 해제되면서 가장 처음에 삽입한 0번째 Test 객체가 두번 해제됩니다. 동적으로 할당한 메모리를 두 번 해제하면서 런타임 오류가 발생하는 듯한데, 혹시 std::list 클래스에서 담겨있는 객체의 소멸자를 호출하면서 발생하는 일인가요?
제가 알기로는 std::list 클래스에서 직접 소멸자를 호출하지 않는것으로 알고 있는데...
std::list에 객체 삽입시 위치를 std::list.begin()으로 하면 문제는 해결됩니다만, 리스트의 순서가 거꾸로 됩니다.

얼마전에 프로젝트를 수행하다가 이 문제를 만났다가 결국 해결하지 못하고 우회하는 방향으로 프로그램을 작성했는데, 알고 넘어가야겠다 싶어서요.

singlet의 이미지

list.begin() 을 쓰면 문제가 해결된다고 하셨는데, 사실은 해결된 것이 전혀 아닙니다. 동적 할당된 메모리는 여전히 두 번씩 해제되고 있으나 라이브러리가 잡아내지 못할 뿐입니다. (해제되는 메모리의 주소를 직접 찍어보세요.) 질문하신 내용은 흔한 문제라서 C++ FAQ에 들어가 있습니다.

http://www.parashift.com/c++-faq-lite/coding-standards.html#faq-27.10 wrote:
A class with any of {destructor, assignment operator, copy constructor} generally needs all 3

정확하게 뭐가 문제인지는 할당 연산자와 복사 생성자를 추가하다 보면 자연히 아시게 될 겁니다. :)

blueskya의 이미지

흔히 new로 할당하면 모조건 delete 시켜야 하는것으로 생각합니다.

그런데 그건 컴파일러에 따라 다른거죠. 그리고 new로 할당하는것이 클래스인지 멤버변수인지에 따라서도 다르게 생각해야하구요.

윗분께서 링크해주신 faq에 destructor쪽 보시면 이해되실꺼에요.

----------------------------------------------------------------------
인생 뭐있어? 백수로 사는거야~ 가는거야~

----------------------------------------------------------------------
인생 뭐있어? 백수로 사는거야~ 가는거야~

익명사용자의 이미지

위에 답변에 덧붙여, stl 컨테이너는 값 복사를 기준으로 합니다.

메인 함수를

int main()
{
    Test a, b, c;
 
    {
        TestList list;
 
        list.push_back(a);
        list.push_back(b);
        list.push_back(c);
 
        for(TestList::iterator it=list.begin(); it!=list.end(); it++)
            cout << (*it).get_cid() << endl;
    }
 
    return 0;
}

같이 해보시면 금방 아실 겁니다. push_front, push_back 이 기본으로 제공되니 간편하게 사용하세요:)
ssehoony의 이미지

deep copy 와 shallow copy 의 문제인데요.
list에 있는 test class 의 객체 들은 정상적으로 한번씩 소멸자가 호출되고 끝난겁니다.
마지막 소멸자 호출은 list 에 있는 객체가 아니고 main 함수는 있는 스택에 생선된 a, b, c 의 소멸자 호출인데요.
a, b, c를 list 에 insert 할 때 shallow copy 에 의해서 a, b, c 에 있는 맵버변수 pa 가 복사가 되는데요.
이때 스택의 a->pa 와 list에 복사된 a->pa 는 동일한 heap 을 가르키게 됩니다.
이런 상태에서 main 함수가 종료되면서 list에서 a가 삭제되면의 list에 있는 a->pa 가 delete 되고
나중에 main 의 스택에 있는 a가 삭제되면서 이미 list 의 a->pa 를 다시 스택의 a->pa 를 삭제하려는 시도가
발생하게 되서 double free 가 되는겁니다.

해결법은 test class 의 복사 생성자와 대입 연산자를 deep copy가 되도록 오버라이딩 하면됩니다.

bearchit의 이미지

네 분 모두 감사합니다.
덕분에 많이 배워갑니다~ ^^
자료구조 시간에 교수님께서 마르고 닳도록 말씀하셨던
shallow/deep copy 문제였군요 ^^;;

Copy constructor를 추가하고 operator = 를 오버라이딩 했더니 말끔해졌습니다.
나중을 위해 소멸자도 가상소멸자로 만들었구요.

도움 주셔서 정말로 감사드립니다 ^^

#include <iostream>
#include <list>
 
using namespace std;
 
// std::list에 삽입하기 위한 클래스
class Test
{
protected:
    int *pa;            // int 타입의 동적메모리를 참조할 포인터
    static int id;      // 객체에 id를 할당하기 위한 카운터
 
public:
    // 생성자의 가장 중요한 임무는 int 타입의 동적메모리를 생성하는 것입니다.
    Test() {
        pa = NULL;
 
        pa = new int;
        *pa = id;
        id++;
 
        cout << "[" << *pa << "] Constructor called" << endl;
    }
 
    Test(const Test& test) {
        pa = new int;
        *pa = *(test.pa);
    }
 
    // 소멸자는 생성자에서 생성된 int 타입의 메모리를 해제합니다.
    virtual ~Test() {
        cout << "[" << *pa << "] Destructor called" << endl;
        delete pa;
    }
 
public:
    Test operator = (const Test& test) {
        if(pa == NULL)
            pa = new int;
        *pa = *(test.pa);
    }
 
    int get_cid() const { return *pa; }        // 객체 id 반환
};
 
int Test::id = 0;
 
typedef list<Test> TestList;
 
int main()
{
    TestList list;
    Test a, b, c;
 
    // std::list에 생성한 3개의 객체를 삽입합니다
    list.push_back(a);
    list.push_back(b);
    list.push_back(c);
 
    // std::list에 담긴 모든 객체의 id를 출력합니다
    TestList::iterator it;
 
    for(it=list.begin(); it!=list.end(); it++)
        cout << (*it).get_cid() << endl;
 
    // a, b, c 해제 및 list 해제
    return 0;
}

실행결과

[0] Constructor called
[1] Constructor called
[2] Constructor called
0
1
2
[2] Destructor called
[1] Destructor called
[0] Destructor called
[0] Destructor called
[1] Destructor called
[2] Destructor called

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.