ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Effective C++] new와 delete를 내 맘대로
    프로그래밍 기초/C++ 2022. 8. 30. 07:20

      new와 delete를 내 맘대로 

    new 처리자의 동작 원리를 제대로 이해하자

    예외 발생 시 호출되는 함수

     C++에는 예외 발생 시 사용자의 함수를 호출해주는 예외 지정이라는 기능이 존재한다.


    void outOfMem()

    {

        std::cerr << "Unable to satisfy request for memory\n";

        std::abort();

    }

    int main()

    {

        std::set_new_handler(outOfMem);

    }


     

    이러한 함수를 구현할 때는 다음 중 하나의 동작이 반드시 이뤄져야 한다.

    • 사용할 수 있는 메모리를 더 많이 확보한다.
    • 다른 new 처리자를 설치한다.
    • new 처리자의 설치를 제거한다.
    • 예외를 던진다.
    • 복귀하지 않고 프로그램을 종료한다.

     

    new 및 delete를 언제 바꿔야 좋은 소리를 들을지를 파악해 두자

    new, delete를 바꾸는 이유

    일반적으로 다음과 같은 이유로 new와 delete를 새로 만들곤 한다.

    • 잘못된 힙 사용을 탐지하기 위해
      • 한 번 new 한 메모리를 두 번 이상 delete 하는 것을 막기 위해 메모리 목록을 저장하는 식으로 사용한다.
      • 메모리를 넘어서 쓰는 것을 막기 위해 메모리 앞, 뒤에 탐지용 바이트 패턴을 넣어두는 식으로 에러를 탐지한다.
    • 효율을 향상 시키기 위해
      • 기본 제공 new, delete는 보편적인 상황을 위해 만들어졌다.
      • 개발자가 자신의 프로그램의 동적 메모리 할당 성향을 잘 파악하고 있으면 새로 new, delete를 만드는 게 더 나을 수 있다.
    • 동적 할당 메모리의 실제 사용에 관한 통계 정보를 수집하기 위해
      • 기본 제공 new, delete에서는 할당 방식, 처리 방식 등을 쉽게 파악할 수 없다.
      • 이 것을 쉽게 파악하고 관리하기 위해 new, delete를 만든다.

     

    그러나 새로 new, delete를 구현했을 때 통상적으로 쓰이는 관례를 제쳐두더라도 바이트 정렬을 맞춰줘야 한다는 문제가 발생한다.

     

    바이트 정렬은 포인터는 4의 배수에 해당하는 주소로 맞춰야 하고 double은 8의 배수에 해당하는 주소에 맞춰야 한다는 규칙인데 이 규칙을 어기는 경우 심각하게는 프로그램이 중단될 수도 있고 조금 유연한 아키텍처라도 속도가 크게 느려지게 된다.

     

    ※ 잘 동작하는 관리자를 만들기는 어려우므로 정말 필요한 게 아니라면 굳이 만들 이유가 없다.

     

    new 및 delete를 작성할 때 따라야 할 기존의 관례를 잘 알아 두자

    operator new의 관례

    new는 몇 가지 관례가 존재하는데 먼저 0바이트가 요구되었을 때도 적법한 포인터를 반환해야 한다는 것이다.

    이에 대해서는 0바이트가 요구될 때는 1바이트로 가정해서 작업을 진행한다.


    void* operator new(std::size_t size) throw(std::bad_alloc)

    {

        using namespace std;

        if (size == 0) {

            size = 1;

        }

        while(true) {

            if (할당 성공)

                return 할당된 메모리의 포인터;

     

            // 할당 실패 시 new 처리자 함수가 있으면 그것을 실행, 아니면 bad_alloc 예외를 발생시킨다.

        }

    }


     

    또, operator new에는 무한 루프가 들어가서 두 번 이상 할당을 시도하게 되는데 위의 함수에서 while(true) 부분에서 확인할 수 있다.

     

    ※ 무한 루프에 갇히지 않기 위해서는 new 처리자 함수 쪽에서는 네 가지 동작 중 하나를 반드시 해 줘야 한다.

     

    operator new가 파생되었을 때 Base클래스와 Derived클래스의 크기가 다르다면 operator new가 정상적으로 동작하지 않을 수 있다. 따라서 크기를 검사해서 크기가 다르면 기존의 operator new를 호출해줘야 한다.


    void* Base::operator new(std::size_t size) throw(std::bad_alloc)

    {

        if (size != sizeof(Base))

            return ::operator new(size);

        ...

    }


     

    operator delete의 관례

    C++에서는 널 포인터에 대한 delete 적용이 항상 안전해야 한다는 것 외에는 크게 관례가 존재하지 않는다.


    void operator delete(void* rawMemory) throw()

    {

        if (rawMemory == 0) return;

        ...

    }


     

    가상 소멸자가 없는 기본 클래스에서 파생된 클래스를 제거할 때 operator delete로 전달되는 size가 엉망일 수 있다. 따라서 클래스를 파생시킬 때는 반드시 가상 소멸자를 사용해야 한다.

     

    위치 지정 new를 작성한다면 위치 지정 delete도 같이 준비하자

    위치 지정 new 란?

    위치 지정 new는 기본적인 operator new에 새로운 매개변수를 전달해주는 형태의 new를 말한다.


    class Widget {

    public:

       ...

        static void* operator new(std::size_t size, std::ostream& logStream) throw(std::bad_alloc);

    };


     

    위치 지정 new와 delete가 짝이 되지 않으면 생기는 문제

    C++의 런타임 시스템은 new에서 에러가 발생했을 때 자동적으로 operator delete를 호출해준다.

    그런데 여기에는 한 가지 조건이 붙는데 operator new와 매개변수의 개수와 타입이 동일한 버전의 operator delete를 호출한다는 것이다.

     

    ※ 위치 지정 new를 사용했다면 위치 지정 delete를 함께 사용해야 한다.

     

    그런데 위치 지정 delete에서 에러가 발생했다면 기본 형태의 delete를 호출하게 되는데 표준 형태의 operator delete가 존재하지 않으면 정상적으로 처리가 되지 않을 것이다.

     

    ※ 위치 지정 new와 연관이 있다면 표준 형태의 operator delete도 마련해야 한다.

     

    댓글

Designed by Tistory.