Qaupot Blog
Software Engineering, Trip

202. 인스턴스 / new / delete

🕐 Sun, 16 Feb 2014 09:00:00 GMT 🕓 Thu, 26 Aug 2021 13:02:00 GMT

인스턴스 (instance)

인스턴스는 추상화 개념이 실제 구현된 것을 말합니다.

  • 추상화 개념은 우리가 추상화 하여 설계한 클래스를 의미
  • 실제 구현은 메모리 상에 실존 하는 데이터를 의미

Person 클래스는 사람을 추상화 하여 설계한 클래스입니다.

int main(int argc, char** argv)
{
   Person tom,alex;
}

Person 클래스로 tom 과 alex 라는 변수를 만들었습니다.
tom 과 alex 는 각각 자신만의 메모리 공간을 갖습니다. 즉, Person 의 Instance 입니다.

new / delete

C++ 에서는 인스턴스가 보다 잘 표현되도록 특별한 키워드를 제공합니다.
new 는 동적 메모리 할당을 대신하며, delete 는 메모리 해제를 대신합니다.

Person* tom = new Person;
delete tom;

new 와 delete 는 클래스가 아닌 기본 자료형이나 구조체에도 사용될 수 있습니다.

  • new 와 delete 는 C++ 키워드이기에, 별도의 헤더를 포함하지 않아도 사용할 수 있습니다.
Person* tom = new Person[4]; 
delete [] tom;

배열에 대해 선언할 경우, 위와 같은 형태로 사용할 수 있습니다.

malloc 과 free 그리고 new 와 delete 를 혼용하여 사용할 경우 문제가 발생할 수 있습니다. 주의가 필요합니다.

  • malloc 과 free 는 단순히 요청한 크기 만큼의 메모리 공간을 돌려줍니다.
  • new 와 delete 는 생성자와 소멸자를 호출하며, 클래스 구현에 필요한 부가적인 메모리를 추가로 할당합니다.
    • 가상 함수 테이블 설명 문서를 참조해 주세요.

다차원 배열에 대한 new / delete

C++에서 new delete 를 이용해 다차원 배열을 직접적으로 선언하거나 제거하는 방법은 없습니다. 따라서, 2차원 이상의 배열을 사용하기에는 다소 불편할 수 있습니다.

먼저 포인터 배열을 만든 후 1차원 배열을 연결하는 방법이 있습니다.

  • 이 경우 연속된 하나의 메모리라 보장 할 수 없습니다.
  • 포인터 연산으로 배열을 탐색하는 경우 안전을 보장할 수 없습니다.
#include <iostream>

int main(int argc, char** argv)
{
    // array[20][20]
    int** array = new int*[20];
    for (int i = 0; i < 20; i++)
    {
        array[i] = new int[20];
    }

    array[2][3] = 3;
    printf("%d", array[2][3]);
    return 0;
}

다른 방법으로는 큰 1차원 배열을 선언하고, 산술적으로 index 값을 계산할 수 있습니다.

  • 연산자 오버로딩 등을 이용해서 처리하면 조금 더 편리하게 사용할 수 있습니다.
#include <iostream>
#define ArrayPos(x,y,ySize) (x*ySize)+y 

int main(int argc, char** argv)
{
    // array[20][20]
    int* array = new int[20*20];

    array[ArrayPos(2,3,20)] = 3;
    printf("%d", array[ArrayPos(2,3,20)]);
    return 0;
}

또 다른 방법으로는 역시 마찬가지로 나중에 다룰 template class 를 활용할 수 있습니다.
array 나 vector 등의 Standard template library 를 활용하면 부가적인 코드량을 줄일 수 있습니다.

#include <iostream>
#include <array>

int main(int argc, char** argv)
{
    // array[20][20]
    std::array<std::array<int, 20>, 20> array;

    array[2][3] = 3;
    printf("%d", array[2][3]);
    return 0;
}

new / delete testing code

new 와 delete 의 배열 선언시 발생하는 일을 추적하기 위한 간단한 예제 코드입니다.

실제 메모리가 할당되고 제거된 것을 직접 확인하려면 별도의 개발도구들을 사용할 필요가 있으므로, 여기서는 '생성자' 와 '소멸자' 를 이용해 추적합니다.

기본적인 정의대로 객체의 생성시와 소멸시 메모리가 할당되거나 소멸되고, 이때 반드시 생성자와 소멸자가 호출이 보장된다고 가정합시다.

  • 즉, 별도의 외부 원인이 개입되지 않는다고 가정한다는 전제를 둡니다.
#include <iostream>

class TestingClass
{
private:
    int val;
public:
    TestingClass()
    {
        printf("Constructor : %x\n",this);
    }

    ~TestingClass()
    {
        printf("Destructor : %x\n", this);
    }
};

int main(int argc, char** argv)
{
    printf("[Case1]\n");
    printf("new TestingClass[5] \n");
    TestingClass* Case1 = new TestingClass[5];

    printf("\ndelete[] testingClass \n");
    delete[] Case1;

    printf("\n[Case2]\n");
    printf("new TestingClass*[2] \n");
    TestingClass** Case2 = new TestingClass*[2];
    for (int i = 0; i < 2; i++)
    {
        Case2[i] = new TestingClass[2];
    }

    printf("\ndelete[] testingClass \n");
    delete[] Case2;
}
new TestingClass[5]
Constructor : 129a79c
Constructor : 129a7a0
Constructor : 129a7a4
Constructor : 129a7a8
Constructor : 129a7ac

delete[] testingClass
Destructor : 129a7ac
Destructor : 129a7a8
Destructor : 129a7a4
Destructor : 129a7a0
Destructor : 129a79c

new TestingClass*[2]
Constructor : 1298f34
Constructor : 1298f38
Constructor : 129a79c
Constructor : 129a7a0

delete[] testingClass

결과에 표기되는 값은 %x로 출력한 this 포인터의 값으로, 해당 객체의 주소 값입니다.

Case1는 1차원 배열입니다.

  • 각 객체는 4byte 간격으로 생성되었고, 정확히 5번 호출되었습니다.
  • delete[] testingClass 를 호출하면, 생성될때의 반대의 순서로 소멸자가 호출됩니다.

Case2는 위에서 언급한 방법을 통해 만든 2차원 배열입니다.

  • Case2[i] = new TestingClass[2];과 같은 하위 1차 배열은 연속적입니다.
  • 하나의 배열이 아닌 여러 개의 배열을 묶어놓은 것에 불과합니다.
  • delete[]는 하위 배열을 해제하지는 않습니다.
TestingClass** Case2 = new TestingClass*[2];

위 코드는 'TestingClass 포인터' 배열을 선언합니다.

Case2[i] = new TestingClass[2];

'TestingClass' 배열을 'TestingClass 포인터' 배열인 Case2에 담습니다.

  • new 의 리턴 값은 할당된 배열의 첫 주소입니다.
  • 배열의 이름은 해당 배열의 첫 주소값을 가르키는 포인터이기도 합니다.
delete [] Case2;

'TestingClass 포인터' 배열을 해제 합니다.

  • 'TestingClass 포인터' 배열이 가지고 있는 값인 'TestingClass' 배열의 주소들에 대해 해제가 이루어지지는 않습니다.
  • 명시적으로 하위 배열부터 직접 해제해야만 합니다.

따라서 Case2는 TestingClass 의 어떠한 소멸자도 호출되지 않았으며, 'TestingClass 포인터' 배열은 해제되었지만, 하위의 'TestingClass' 배열 메모리는 해제 되지 않았습니다.

  • 'TestingClass 포인터' 는 클래스 객체가 아닌 숫자 값(주소)이며, 소멸자가 없습니다.
이 블로그는 개인 블로그입니다. 게시글은 오류를 포함하고 있을 수 있지만, 저자는 오류를 해결하기 위해 노력하고 있습니다.
게시글에 별도의 고지가 없는 경우, 크리에이티브 커먼즈 저작자표시-비영리-변경금지 4.0 라이선스를 따릅니다.

This blog is personal blog. published posts may contain some errors, but author doing efforts to clear errors.
If post have not notice of license, it under creative commons Attribution-NonCommercial-NoDerivatives 4.0.