좋 은 C++프로 그래 밍 습관 을 기 르 는 메모리 관리 에 대한 상세 한 설명

17157 단어 C++메모리 관리
첫머리 도독        
이 시 리 즈 는 과학 보급 도서 로 자리 잡 았 지만 초보 자 들 이 참고 할 수 있 을 뿐만 아니 라 늙 은 새들 의 반성 과 공감 을 불 러 일 으 킬 수 있다 고 믿 습 니 다.여러분 의 소중 한 의견 과 피드백 을 환영 합 니 다^ ^
이 장의 주요 내용 을 시작 하기 전에 본 자 리 는 먼저 작은 지면 으로 좋 은 업무 습관 인 축적,추출 과 수정 을 논술 한다.일과 학습 과정 에서 배 운 지식 을 효과 적 인 방식 으로 쌓 아 자신의 지식 고 를 형성 하고 지식 양 이 확대 되면 서 양 적 변화 에서 질 적 변화 로 향상 시 킬 수 있다.또한 지식 을 계속 추출 해 야 한다.자신의 지식 면 이 확대 되 고 수준 이 향상 되면 서 기 존의 지식 창고 에 단편 적 이 고 한계 가 있 으 며 서 툴 고 심지어 잘못 이 존재 한 다 는 것 을 알 게 될 것 이다.이 럴 때 는 지식 고 를 최적화 정리 하 는 태도 와 끈기 가 필요 하 다.
아마도 이상 의 사람들 은 과거 에 실시 하고 그 이 치 를 알 고 있 었 을 것 이다.그러나 자신 은 자신 에 게 여러 가지 당당 한 핑 계 를 대고 시간 을 들 이지 않 았 다.이렇게 말 하면 기술 의 길 은 걷 기 어렵다.이 업 계 는 두 가지 기본 적 인 요구 가 있다.1.소프트웨어 개발 업무 자체 에 큰 관심 이 있다.2.외로움 을 참 을 수 있다.둘 중 하나 가 없어 서 는 안 됩 니 다.그렇지 않 으 면 젊 을 때 일찍 전업 하 세 요.소프트웨어 업계 의 판매,제품 또는 관리 로 전환 하 셔 도 됩 니 다.어쨌든 개발 은 하지 마 세 요^ ^
--------------------------------------------------------------------------------
질문
C/C++의 메모리 관리 하면 대부분의 사람들의 머 릿 속 에 new/delete/malloc/free 등 몇 개의 무 서운 단어 가 떠 오 르 죠?확실히 C/C++의 수 동 메모리 관 리 는 다른 언어 와 구별 되 는 큰 특징 이 고 다른 언어 에서 C/C++로 전환 하려 는 사람들 앞 에 장벽 이 서 있 는 것 과 같다.이 를 통 해'C++인기 저하'와'쓰레기 수 거 메커니즘 을 도입 해 야 하 는가'등 관련 이 슈 에 대한 각종 포럼 의 갑론을박 도 일 고 있다.이 자 리 는 이러한 논쟁 을 무시 해 왔 다.사실 이 자 리 는 C++의 발전 과 운명 에 관심 이 없 는 것 이 아니 라 반대로 이 자 리 는 매우 관심 을 가지 고 있다.현재 의 안목 으로 볼 때 C++몸 에 얼마나 많은 경상 이 있 든 지 간 에 C++위원회 의 나리 들 과 C++컴 파일 러 업 체 의 사내 들 이 어떻게 꼬리 를 잡 는 지 알 수 있다.결국 가장 사랑 하 는 것 은 가장 사랑 하 는 것 이 고,불완전한 아름다움 도 아름 답 기 때문에 설명 하지 않 는 다.이 자리 가 이런 논쟁 에 관심 이 없 는 이 유 는 한 가지 언어 가 인생 과 같 고 생명 주기 가 있 으 며 몰락 은 빠 른 문제 일 뿐 낡은 사물 은 항상 새로운 사물 로 대체 되 기 때문이다.이것 은 객관 적 인 규칙 이 불가피 하 다.진시황 도 결국 불로장생 의 구슬 을 찾 지 못 했 지 않 습 니까?빛 을 발 하고 열 을 낸 적 이 있다 면 가치 가 있 을 때 대중 에 게 사 용 될 수 있다 는 것 만으로 도 여한 이 없다.이 자 리 는 어떤 언어 도 배척 하지 않 는 반면 새로운 언어의 탄생 에 관심 이 많다 는 태 도 를 밝 혀 야 한다.그들의 특징 을 알 아 보고 어떤 문 제 를 해결 하 는 데 도움 이 되 는 지 살 펴 보 겠 다.요 몇 년 동안 업무 수요 로 인해 본 자 리 는 자바 와 일부 동적 언어(그들 은 확실히 많은 문 제 를 해결 할 수 있다)를 가장 많이 사 용 했 지만 C/C++는 더 이상 사용 하지 않 았 다.
응,멀리 갔 어.우리 본론 으로 돌아 가자.C/C++의 메모리 관 리 는 보기 만 해도 무 서운 것 같 습 니 다.화면 가득 한 new/delete/malloc/free,OutPut 창의 끝 없 는 Memory Leak 경고,프로그램의 기괴 한 0x 0000004 지침 이상,마치 그 해 우리 가 함께 울 었 던 날 로 돌아 가 는 것 같 습 니 다.당신 은 잡 을 수 있 습 니까?사실 현실 은 네가 생각 하 는 것 만큼 그렇게 나 쁘 지 않다.조금 만 신경 쓰 면 돼,맞 아!조금 뿐 입 니 다.C++클래스 로 메모리 에 접근 하면 대부분의 고민 을 해결 하고 평생 이익 을 얻 을 수 있 습 니 다.윈도 프로그램 을 예 로 들 면 다음 과 같은 몇 가지 메모리 관리 방식 이 있 습 니 다.
•가상 메모리(Virtual Memory)•기본 더미 와 개인 더미(Process Heap&Private Heap)•메모리 맵 파일(File Mapping)•프로 세 스 스 스 택(Heap,사실은 malloc()또는 기본 new 연산 자 를 사용 하여 Process Heap 에서 고 기 를 조금씩 자 르 는 것 입 니 다^^)•스 택(Stack,메모 리 는 호출 자 나 호출 자가 자동 으로 관리 합 니 다)오늘 우리 의 주 제 는 패키지 입 니 다.모든 메모리 모델 의 개념 과 API 사용 방식 에 대해 서 는 말 하지 않 겠 습 니 다.구 글 은 바로 알 고 있 습 니 다.사실 C++로 상기 4 가지 메모리 에 접근 하 는 원 리 는 차이 가 많 지 않 습 니 다.구조 함수 나 다른 조작 함수 에서 메모 리 를 분배 한 다음 에 분석 함수 에서 메모리 가 정확하게 방출 되도록 하 는 것 입 니 다.가상 메모리,기본 더미 와 개인 더미 의 조작 방식 이 비슷 합 니 다.여 기 는 일일이 보 여 주지 않 습 니 다.관심 있 는 친 구 는 며칠 전에 발표 한 아무 도 관심 을 갖 지 않 는 글 을 참고 할 수 있 습 니 다.메모리 맵 파일 의 패 키 징 에 대해 서도 조금 만 소개 하 겠 습 니 다.주파수 가 가장 높 은 malloc()와 new 패 키 징 을 사용 하 는 것 을 주로 토론 합 니 다.
--------------------------------------------------------------------------------
 메모리 맵 파일
다음 코드 는 File Mapping 핸들 과 File Mapping 에서 비 친 메모 리 를 각각 CFileMapping 과 CShareMemory 에 봉 하여 CShareMemory 를 직접 사용 하면 File Mapping 과 File Mapping 을 비 추 는 메모 리 를 만 들 수 있 습 니 다.

class CFileMapping
{
public:
    CFileMapping(   
                    LPCTSTR lpszName,
                    DWORD dwMaximumSizeLow,
                    DWORD dwMaximumSizeHigh                            = 0,
                    HANDLE hFile                                    = INVALID_HANDLE_VALUE,
                    DWORD flProtect                                    = PAGE_READWRITE,
                    LPSECURITY_ATTRIBUTES lpFileMappingAttributes    = NULL
                )
    {
        m_hMap    = ::CreateFileMapping    (
                                            hFile,
                                            lpFileMappingAttributes,
                                            flProtect,
                                            dwMaximumSizeHigh,
                                            dwMaximumSizeLow,
                                            lpszName
                                        );
        ASSERT(IsValid());
    }

    ~CFileMapping()
    {
        if(IsValid())
            VERIFY(::CloseHandle(m_hMap));
    }

    LPVOID ViewMap    (
                        DWORD dwNumberOfBytesToMap,
                        DWORD dwFileOffsetLow,
                        DWORD dwFileOffsetHigh    = 0,
                        DWORD dwDesiredAccess    = FILE_MAP_ALL_ACCESS
                    )
    {
        return ::MapViewOfFile    (
                                    m_hMap,
                                    dwDesiredAccess,
                                    dwFileOffsetHigh,
                                    dwFileOffsetLow,
                                    dwNumberOfBytesToMap
                                );
    }

    BOOL UnViewMap(LPCVOID lpBaseAddress)
    {
        return ::UnmapViewOfFile(lpBaseAddress);
    }

    operator HANDLE    ()    {return m_hMap;}
    BOOL IsValid    ()    {return m_hMap != NULL;}

private:
    HANDLE m_hMap;

    DECLARE_PRIVATE_COPY_CONSTRUCTOR(CFileMapping)
};

class CShareMemory
{
public:
    CShareMemory(DWORD dwSize, LPCTSTR lpszName = NULL)
    : m_fm(lpszName, dwSize)
    {
        ASSERT(dwSize > 0);
    }

    ~CShareMemory()
    {
        for(set<ULONG_PTR>::const_iterator it = m_set.begin(); it != m_set.end(); ++it)
        {
            LPVOID pV = (LPVOID)*it;
            ASSERT(pV);

            m_fm.UnViewMap(pV);
        }

        m_set.clear();
    }

    LPVOID Alloc(DWORD dwNumberOfBytesToMap, DWORD dwFileOffsetLow)
    {
        LPVOID pV = m_fm.ViewMap(dwNumberOfBytesToMap, dwFileOffsetLow);

        if(pV) m_set.insert((ULONG_PTR)pV);

        ASSERT(pV);
        return pV;
    }

    BOOL Free(LPCVOID lpBaseAddress)
    {
        ASSERT(lpBaseAddress);

        set<ULONG_PTR>::iterator it = m_set.find((ULONG_PTR)lpBaseAddress);

        if(it != m_set.end())
            m_set.erase(it);

        return m_fm.UnViewMap(lpBaseAddress);
    }

private:

    CFileMapping    m_fm;
    set<ULONG_PTR>    m_set;

    DECLARE_PRIVATE_COPY_CONSTRUCTOR(CShareMemory)
};

 
세심 한 친 구 는 사실 이렇게 포장 하 는 것 이 단점 이라는 것 을 알 게 될 것 이다.우선,CShare Memory 는 메모리 공유 만 할 수 있 고 실제 파일 에 투사 할 수 없다(hFile 은 영원히 INVALID 이다.HANDLE_VALUE);둘째,CShareMemory 의 Alloc()와 Free()방법 을 한층 더 봉인 할 수 있 고,봉인 류 의 석조 함 수 를 이용 하여 Free()를 자동 으로 호출 하면'set m_set"이 속성 입 니 다.셋째,CFileMapping 도 파일 핸들 을 함께 밀봉 할 수 있 습 니 다.그러면 Create File()부터 Create FileMapping()까지 모두 제 어 됩 니 다.이 불완전한 봉 투 는 반면교사 로 삼 아 라^ ^--------------------------------------------------------------------------------
malloc()시리즈 함수
많은 사람들 이 C++에서 malloc()대신 new 연산 자 를 사용 하 라 고 건의 합 니 다.new 형식 이 안전 하기 때문에 구조 함수 와 석조 함수 등 을 자동 으로 호출 합 니 다.이 자리 에 대해 약간의 이의 가 있 습 니 다.어떤 상황 에서 malloc()는 new 보다 더 좋 습 니 다.효율 적 인 측면 에서 우 리 는 따 지지 않 아 도 됩 니 다.이러한 데 이 터 는 malloc()를 사용 하기에 매우 적합 합 니 다.new 로 분 배 된 메모리 로 멈 춰 서 도대체 delete,delete[]를 사용 하 는 지 생각해 야 합 니 다.:delete,:delete,:delete[]중의 어느 방출,malloc()가 분 배 된 메모리 에 대해 생각 할 필요 가 없습니다.free()는 천 하 를 책임 집 니 다.하물며 사람들 은 realloc()가 있어 서 편리 하 게 메모 리 를 재 조정 할 수 있 습 니 다.당신 은'renew'가 있 습 니까?한 마디 로 말 하면 malloc()는 확실히 존재 할 필요 가 있 습 니 다.다음 에 우리 가 어떻게 포장 하 는 지 보 겠 습 니 다.코드 를 보 세 요.

// T                 : ( )
// MAX_CACHE_SIZE    : , sizeof(T) , ,
//                     buffer
template<class T, size_t MAX_CACHE_SIZE = 0>
class CBufferPtrT
{
public:
    explicit CBufferPtrT(size_t size = 0, bool zero = false)    {Reset(); Malloc(size, zero);}
    explicit CBufferPtrT(const T* pch, size_t size)    {Reset(); Copy(pch, size);}
    //
    CBufferPtrT(const CBufferPtrT& other)    {Reset(); Copy(other);}
    template<size_t S> CBufferPtrT(const CBufferPtrT<T, S>& other)    {Reset(); Copy(other);}

    ~CBufferPtrT() {Free();}

    T* Malloc(size_t size = 1, bool zero = false)
    {
        Free();
        return Alloc(size, zero, false);
    }

    T* Realloc(size_t size, bool zero = false)
    {
        return Alloc(size, zero, true);
    }

    void Free()
    {
        if(m_pch)
        {
            free(m_pch);
            Reset();
        }
    }

    template<size_t S> CBufferPtrT& Copy(const CBufferPtrT<T, S>& other)
    {
        if((void*)&other != (void*)this)
            Copy(other.Ptr(), other.Size());

        return *this;
    }

    CBufferPtrT& Copy(const T* pch, size_t size)
    {
        Malloc(size);

        if(m_pch)
            memcpy(m_pch, pch, size * sizeof(T));

        return *this;
    }

    // buffer
    template<size_t S> CBufferPtrT& Cat(const CBufferPtrT<T, S>& other)
    {
        if((void*)&other != (void*)this)
            Cat(other.Ptr(), other.Size());

        return *this;
    }

    // buffer
    CBufferPtrT& Cat(const T* pch, size_t size = 1)
    {
        size_t pre_size = m_size;
        Realloc(m_size + size);

        if(m_pch)
            memcpy(m_pch + pre_size, pch, size * sizeof(T));

        return *this;
    }

    template<size_t S> bool Equal(const CBufferPtrT<T, S>& other) const
    {
        if((void*)&other == (void*)this)
            return true;
        else if(m_size != other.Size())
            return false;
        else if(m_size == 0)
            return true;
        else
            return (memcmp(m_pch, other.Ptr(), m_size * sizeof(T)) == 0);
    }

    bool Equal(T* pch) const
    {
        if(m_pch == pch)
            return true;
        else if(!m_pch || !pch)
            return false;
        else
            return (memcmp(m_pch, pch, m_size * sizeof(T)) == 0);
    }

    T*    Ptr()    {return m_pch;}
    const T*    Ptr()    const    {return m_pch;}
    T&    Get(int i)    {return *(m_pch + i);}
    const T&    Get(int i)    const    {return *(m_pch + i);}
    size_t    Size()    const    {return m_size;}
    bool    IsValid()    const    {return m_pch != 0;}
    // ,
    operator    T*    ()    {return Ptr();}
    operator const    T*    ()    const    {return Ptr();}
    // ,
    T& operator    []    (int i)    {return Get(i);}
    const T& operator    []    (int i)    const    {return Get(i);}
    bool operator    ==    (T* pv)    const    {return Equal(pv);}
    template<size_t S> bool operator    ==    (const CBufferPtrT<T, S>& other)    {return Equal(other);}
    //
    CBufferPtrT& operator    =    (const CBufferPtrT& other)    {return Copy(other);}
    template<size_t S> CBufferPtrT& operator    =    (const CBufferPtrT<T, S>& other)    {return Copy(other);}

private:
    void Reset()    {m_pch = 0; m_size = 0; m_capacity = 0;}
    size_t GetAllocSize(size_t size)    {return max(size, min(size * 2, m_size + MAX_CACHE_SIZE));}

    T* Alloc(size_t size, bool zero = false, bool is_realloc = false)
    {
        if(size >= 0 && size != m_size)
        {
            size_t rsize = GetAllocSize(size);
            if(size > m_capacity || rsize < m_size)
            {
                m_pch = is_realloc                            ?
                    (T*)realloc(m_pch, rsize * sizeof(T))    :
                   (T*)malloc(rsize * sizeof(T))            ;

                if(m_pch || rsize == 0)
                {
                    m_size        = size;
                    m_capacity    = rsize;
                }
                else
                    Reset();
            }
            else
                m_size = size;
        }

        if(zero && m_pch)
            memset(m_pch, 0, m_size * sizeof(T));

        return m_pch;
    }

private:
    T*        m_pch;
    size_t    m_size;
    size_t    m_capacity;
};

// buffer typedef
typedef CBufferPtrT<char>            CCharBufferPtr;
typedef CBufferPtrT<wchar_t>        CWCharBufferPtr;
typedef CBufferPtrT<unsigned char>    CByteBufferPtr;
typedef CByteBufferPtr                CBufferPtr;

#ifdef _UNICODE
    typedef CWCharBufferPtr            CTCharBufferPtr;
#else
    typedef CCharBufferPtr            CTCharBufferPtr;
#endif

응.여기 서 왜 두 개의 복사 구조 함수 와 할당 연산 자 를 다시 불 러 와 야 하 는 지 설명해 야 한다.먼저 컴 파일 러 는 서로 다른 템 플 릿 매개 변수 로 서로 다른 종 류 를 생 성 한다.즉,CBufferPtrT과 CBufferPtrT는 서로 다른 종류 로 간주 된다.또한,C++컴 파일 러 는 각 클래스 에 복사 구조 함수 와 할당 연산 자 를 다시 불 러 오 는 기본 구현(얕 은 복사)을 제공 합 니 다.따라서 상기 첫 번 째 복사 구조 함수 와 할당 연산 자 리 셋 은 컴 파일 러 를 바 꾸 는 기본 적 인 실현 이 고 두 번 째 복사 구조 함수 와 할당 연산 자 리 셋 은 다른 클래스 에서 본 클래스 로 전환 하 는 것 입 니 다.
이 패 키 징 재 에 만족 합 니 다.이것 은 일반적인 malloc()패키지 가 아니 라'색인 접근 을 지원 하 는 형식 이 안전 한 동적 버퍼'로 볼 수 있 습 니 다.이 를 하나의 socket 통신 클래스 에 멤버 속성 으로 두 면 여러 스 레 드 와 여러 방법 으로 접근 하 는 수신 버퍼 와 버퍼 를 보 내 는 역할 이 가장 적합 합 니 다(당연히 스스로 동기 화 해 야 합 니 다).여러분 은 아래 의 테스트 예 를 시험 해 보고 그것 의 용법 을 알 아 볼 수 있 습 니 다.
new & delete
뉴 의 패키지 하면 바로 생각 나 는 게 스마트 지침 이 죠!맞 아,바로 스마트 포인터 야.하지만 STL 이 제공 하 는 autoptr 결함 이 많 습 니 다.먼저 사용 하기 불편 합 니 다.이런 쓰기 도 지원 하지 않 습 니 다."std:auto"ptr pi = new int;”,천리 가 어디 있 느 냐!더 가 증 스 러 운 것 은 배열 지침(delete[필요])을 지원 하지 않 습 니 다.또한 어떤 종류 가 new 연산 자 를 다시 불 러 오 면 사용 하 는 것 도 문제 가 많 고 다른 단점 도 많 습 니 다(잊 어 버 렸 습 니 다^ ^).그러나 C++0x 는 스마트 포인터 에 대해 중대 한 개선 을 한 것 같 습 니 다.인용 수 를 지원 하 는 스마트 포인터 가 있 지만 배열 포인터 와 delete 와 구분 delete 문 제 를 해결 할 지 모 르 겠 습 니 다.어쨌든 아래 코드 에 열 거 된 스마트 포인 터 는 delete/delete[]/:delete/:delete[]를 구분 하 는 것 을 지원 합 니 다.auto 라 고 할 수 있 습 니 다.ptr 의 개량(인용 계수 도 사용 하지 않 았 습 니 다).글 의 편폭 이 너무 길 어서 테스트 용례 가 발표 되 지 않 습 니 다.여러분 이 직접 시도 해 보 세 요.


 int _tmain(int argc, _TCHAR* argv[])
 {
     CBufferPtr buffer;

     unsigned char c1    = 'X';
     unsigned char pc1[] = "123";
     unsigned char pc2[] = "abc";
     buffer.Cat(&c1);
     buffer.Cat(pc1, 3);
     buffer.Cat(pc2, 3);

     CBufferPtrT<unsigned char, 10> buffer2 = buffer;
     buffer2.Cat(buffer);
     buffer2.Realloc(0);

     unsigned char* pc = buffer;
     const unsigned char& c = buffer[5];
     buffer[5] = 'O';

     short i1    = 0x7FFF;
     short pi0[] = {9,9,9};
     short pi1[] = {1,2,3};
     short pi2[] = {4,5,6};
     short pi3[] = {8,8,8};

     CBufferPtrT<short, 10> bufferS(pi0, 3);

     bufferS.Cat(&i1);
     bufferS.Cat(pi1, 3);
     bufferS.Cat(pi2, 3);
     bufferS.Cat(pi3, 3);

     CBufferPtrT<short, 5> bufferS2;
     bufferS2.Malloc(4);

     bufferS2 = bufferS;
     bufferS2.Realloc(30);

     CBufferPtrT<int> bufferI(5, true);

     for(size_t i = 0; i < bufferI.Size(); i++)
         bufferI[i] = i *10;

     bufferI.Malloc();
     bufferI[0] = 123;

     // ,
 // bufferI = bufferS;

     return 0;
 }
 -------------------------------------------------------------------------------- 후기
•메모리 관리 에 대해 서 는 아직 말 하지 않 은 것 이 있 습 니 다.vetor,list,map 같은 용기 의 지침 을 우아 하 게 관리 하 는 방법 입 니 다.이 화 제 는 나중에 STL 을 토론 할 때 상세 하 게 설명 하 겠 습 니 다.이 자리 의 코드 에 서 는 free/delere 라 는 단어 가 거의 보이 지 않 습 니 다(new 는 있 습 니 다―스마트 포인터 에 값 을 부여 할 때^ ^).이 자리 의 경험 에 따 르 면 패 키 징 을 적절하게 이용 하면 많은 번 거 로 움 을 줄 이 고 코드 를 더욱 뚜렷 하고 조리 있 게 하 며 오류 발생 확률 을 낮 출 수 있 습 니 다.물론 포장 은 만능 이 아니다.모든 문 제 를 해결 할 수 없다.관건 은 개인의 집중 과 세심 함 이다.이 코드 글 자 는 자신의 관점 을 제시 하 는데 벽돌 을 던 져 옥 을 끌 어 올 리 고 사람들 로 하여 금 좋 은 프로 그래 밍 습관 을 어떻게 키 우 는 지 생각 하 게 하 는 데 목적 을 둔다.권위 가 아니 라 믿 을 수도 없다.가장 진실 한 지식 은 개인의 가장 직접적인 체험 에서 나 와 야 한다.

좋은 웹페이지 즐겨찾기