C ++에서 메모리 누수를 피하기위한 일반 지침 [닫기]


130

C ++ 프로그램에서 메모리가 누출되지 않도록하는 일반적인 팁은 무엇입니까? 동적으로 할당 된 메모리를 누가 확보해야하는지 어떻게 알 수 있습니까?


26
꽤 건설적인 것 같습니다.
Shoerob

11
이것은 건설적인 것입니다. 그리고 답변은 사실, 전문 지식, 참고 문헌 등에 의해 뒷받침됩니다. 그리고 투표 / 응답 수를보십시오 .. !!
Samitha Chathuranga 4

답변:


40

메모리를 수동으로 관리하는 대신 해당되는 경우 스마트 포인터를 사용하십시오. Boost lib , TR1스마트 포인터를
살펴보십시오 . 또한 스마트 포인터는 이제 C ++ 11 이라는 C ++ 표준의 일부입니다 .


1
g ++를 사용하여 컴파일하려면 param을 추가해야합니다 : -std = c ++ 0x
Paweł Szczur

또는 플래그 값을 사용하여 g ++로 컴파일 할 수 있습니다 -std = c ++ 11
Prabhash Rathore

200

RAII 및 스마트 포인터에 대한 모든 조언을 철저히 보증하지만 약간 더 높은 수준의 팁을 추가하고 싶습니다. 관리하기 가장 쉬운 메모리는 할당하지 않은 메모리입니다. 거의 모든 것이 참조 인 C # 및 Java와는 달리 C ++에서는 가능할 때마다 객체를 스택에 배치해야합니다. Stroustrup 박사를 포함한 여러 사람들이 지적한 것처럼 C ++에서 가비지 수집이 인기가 없었던 주된 이유는 잘 작성된 C ++이 처음에는 많은 가비지를 생산하지 않기 때문입니다.

쓰지 마

Object* x = new Object;

또는

shared_ptr<Object> x(new Object);

그냥 쓸 수있을 때

Object x;

34
나는 이것을 +10으로 줄 수 있으면 좋겠다. 이것은 오늘날 대부분의 C ++ 프로그래머에게 가장 큰 문제이며 C ++보다 Java를 배웠기 때문이라고 생각합니다.
Kristopher Johnson

매우 흥미로운 점 – 다른 언어보다 C ++ 메모리 관리 문제가 훨씬 적은 이유에 대해 궁금해했지만 이제는 이유를 알 수 있습니다. 실제로는 바닐라 C에서와 같이 실제로 스택에 정보를 넣을 수 있습니다.
ArtOfWarfare

Object x를 쓰면 어떻게해야합니까? x를 버리고 싶습니까? x가 기본 방법으로 생성되었다고 말합니다.
Yamcha

3
@ user1316459 C ++을 사용하면 스코프도 즉시 생성 할 수 있습니다. x의 수명을 다음과 같이 중괄호 안에 넣으면됩니다. {Object x; x.DoSomething; }. 마지막 '}'후에 x의 소멸자는 포함 된 모든 리소스를 해제한다고합니다. x 자체가 힙에 할당되는 메모리 인 경우 고유하고 쉽게 정리할 수 있도록 unique_ptr로 래핑하는 것이 좋습니다.
David Peterson

1
로버트 : 예. 로스는 말하지 않았다 "결코 쓰기 [코드 새로 포함]"그는 "[것을]하지 쓰기를 말했다 당신이 할 수있을 때 단지 [스택에 넣어]". 힙에있는 큰 객체는 대부분의 경우 특히 성능 집약적 인 코드에서 계속 올바른 호출입니다.
codetaku

104

RAII 사용

  • 가비지 콜렉션을 잊어 버리십시오 (대신 RAII를 사용하십시오). 가비지 콜렉터도 누출 될 수 있으며 (Java / C #에서 일부 참조를 "널링"하는 것을 잊어 버린 경우), 가비지 콜렉터는 자원을 처리하는 데 도움이되지 않습니다 (손잡이를 획득 한 오브젝트가있는 경우). Java에서 수동으로 수행하지 않거나 C #에서 "dispose"패턴을 사용하면 오브젝트가 범위를 벗어날 때 파일이 자동으로 해제되지 않습니다.
  • "함수 당 하나의 리턴"규칙을 잊어 버리십시오 . 이것은 누출을 피하기위한 좋은 C 조언이지만 예외 사용으로 인해 C ++에서는 구식입니다 (대신 RAII 사용).
  • 반면 은 "샌드위치 패턴은" 좋은 C 조언은, 그것은 C ++에서 구식이되어 있기 때문에 예외의 사용 (대신 사용 RAII)의.

이 게시물은 반복적 인 것으로 보이지만 C ++에서 알아야 할 가장 기본적인 패턴은 RAII 입니다.

boost, TR1 또는 심지어는 낮지 만 (충분히 효율적인) auto_ptr에서 스마트 포인터를 사용하는 법을 배우십시오 (그러나 그 한계를 알아야합니다).

RAII는 C ++에서 예외 안전 및 리소스 처리의 기초이며, 다른 패턴 (샌드위치 등)은 둘 다를 제공하지 않습니다 (대부분의 경우 아무 것도 제공하지 않음).

RAII와 비 RAII 코드의 비교는 아래를 참조하십시오.

void doSandwich()
{
   T * p = new T() ;
   // do something with p
   delete p ; // leak if the p processing throws or return
}

void doRAIIDynamic()
{
   std::auto_ptr<T> p(new T()) ; // you can use other smart pointers, too
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

void doRAIIStatic()
{
   T p ;
   // do something with p
   // WON'T EVER LEAK, even in case of exceptions, returns, breaks, etc.
}

RAII 소개

요약하면 ( Ogre Psalm33 의 의견 이후 ) RAII는 다음 세 가지 개념에 의존합니다.

  • 객체가 생성되면 작동합니다! 생성자에서 리소스를 얻습니다.
  • 객체 파괴로 충분합니다! 소멸자에서 무료 리소스를 사용하십시오.
  • 범위에 관한 모든 것입니다! 범위가 지정된 객체 (위의 doRAIIStatic 예제 참조)는 선언시 생성되며 종료 (반환, 중단, 예외 등)에 관계없이 실행이 범위를 종료하는 순간에 삭제됩니다.

이것은 올바른 C ++ 코드에서 대부분의 객체가로 구성되지 new않고 대신 스택에서 선언 됨을 의미합니다 . 그리고를 사용하여 생성 된 사람들의 new경우 모두 범위가 지정됩니다 (예 : 스마트 포인터에 연결).

개발자는 수동 리소스 처리 (C에서 수행 한 것처럼 또는 Java의 경우 try/ finally를 집중적으로 사용하는 일부 객체)에 신경 쓸 필요가 없으므로 매우 강력합니다 ...

편집 (2012-02-12)

"범위가 지정된 객체는 ... 종료 여부에 관계없이 파괴됩니다"는 전적으로 사실이 아닙니다. RAII를 속이는 방법이 있습니다. terminate ()의 풍미는 정리를 우회합니다. 이와 관련하여 exit (EXIT_SUCCESS)는 옥시 모론입니다.

빌헬름 텔

wilhelmtell 은 그 점에 대해 매우 옳습니다. RAII를 속이는 예외적 인 방법 이 있습니다 .

사람들은 뛰어난 C ++ 코드 등, 종료 종료 뒤덮되지 않기 때문에 방법, 또는 예외의 경우에, 우리는 싶어 처리되지 않은 예외가 그대로 공정 및 코어 덤프 메모리 이미지를 충돌, 그리고 청소 후.

그러나 거의 발생하지 않지만 여전히 발생할 수 있기 때문에 이러한 경우에 대해 알아야합니다.

(누가 호출 terminate또는 exit캐주얼 C에서 ++ 코드는 ... 내가 함께 연주 할 때 그 문제를 해결하는 데 기억? GLUT :이 라이브러리는 멀리 적극적으로 개발자가 배려하지 좋아 ++ C에 대한 일을 어렵게 만들 그것을 디자인으로가는 매우 C 지향 에 대해 스택에 할당 된 데이터 , 또는에 대해 "흥미로운"결정을 가진 적이 자신의 메인 루프에서 돌아 없습니다 ... 나는) 그것에 대해 언급하지 않습니다 .


doRAIIStatic ()이 메모리를 누출하지 않도록 T 클래스가 RAII를 사용해서는 안됩니까? 예를 들어 T p (); p.doSandwich (); 나는 이것에 대해 정말로 많이 모른다.
Daniel O

@Ogre Psalm33 : 댓글 주셔서 감사합니다. 물론 그렇습니다. RAII Wikipedia 페이지에 대한 두 링크와 RAII 란 무엇에 대한 간단한 요약을 추가했습니다.
paercebal

1
@Shiftbit : 우선 순위에 따라 세 가지 방법 : _ _ _ 1. 실제 객체를 STL 컨테이너 안에 넣습니다. _ _ _ 2. STL 컨테이너 안에 객체의 스마트 포인터 (shared_ptr)를 넣습니다. _ _ _ 3. 원시 포인터를 STL 컨테이너 안에 넣지 만 컨테이너를 감싸서 데이터에 대한 액세스를 제어하십시오. 래퍼는 소멸자가 할당 된 객체를 해제하도록하고 래퍼 접근자는 컨테이너에 액세스 / 수정할 때 깨진 것이 없는지 확인합니다.
paercebal

1
@Robert : C ++ 03에서는 자식 또는 부모 함수 (또는 전역 범위)에 소유권을 부여해야하는 함수에서 doRAIIDynamic을 사용합니다. 또는 팩토리를 통해 다형성 객체에 대한 인터페이스를 수신 할 때 (스마트 포인터가 올바르게 작성되면 반환). C ++ 11에서는 객체를 움직일 수 있으므로 스택에 선언 된 객체의 소유권을 부여하는 것이 더 쉽습니다.
paercebal

2
@Robert : ... 스택에서 객체를 선언한다고해서 객체가 내부적으로 힙을 사용하지 않는다는 의미는 아닙니다 (이중 부정에주의하십시오 ... :-) ...). 예를 들어, 작은 문자열 최적화로 구현 된 std :: string은 작은 문자열 (~ 15 자)에 대해 "클래스 스택에"버퍼를 가지며 더 큰 문자열에 대해서는 힙의 메모리에 대한 포인터를 사용합니다. 그러나 외부에서 std :: string은 여전히 ​​스택에서 선언하는 값 유형이며 일반적으로 정수를 사용할 때 사용합니다 (대신 polymorph 클래스의 인터페이스를 사용할 때와 반대).
paercebal

25

부스트의 스마트 포인터 와 같은 스마트 포인터를보고 싶을 것 입니다.

대신에

int main()
{ 
    Object* obj = new Object();
    //...
    delete obj;
}

boost :: shared_ptr은 참조 횟수가 0이되면 자동으로 삭제됩니다.

int main()
{
    boost::shared_ptr<Object> obj(new Object());
    //...
    // destructor destroys when reference count is zero
}

마지막 참고 사항 인 "참조 횟수가 0 일 때 가장 멋진 부분입니다. 따라서 개체를 여러 명 사용하는 경우 개체가 아직 사용 중인지 여부를 추적 할 필요가 없습니다. 공유 포인터, 그것은 파괴된다.

그러나 이것은 만병 통치약이 아닙니다. 기본 포인터에 액세스 할 수 있지만 수행중인 작업에 대한 확신이 없으면 타사 API에 전달하지 않습니다. 많은 경우, 작성 범위가 완료된 후 작업을 수행하기 위해 다른 스레드에 "게시"하는 것들. 이것은 Win32의 PostThreadMessage에서 일반적입니다.

void foo()
{
   boost::shared_ptr<Object> obj(new Object()); 

   // Simplified here
   PostThreadMessage(...., (LPARAM)ob.get());
   // Destructor destroys! pointer sent to PostThreadMessage is invalid! Zohnoes!
}

항상 그렇듯이 모든 도구와 함께 사고 뚜껑을 사용하십시오 ...



11

대부분의 메모리 누수는 개체 소유권과 수명에 대해 명확하지 않은 결과입니다.

가장 먼저 할 일은 가능하면 스택에 할당하는 것입니다. 이것은 어떤 목적으로 단일 객체를 할당해야하는 대부분의 경우를 다룹니다.

객체를 '새로 작성'해야하는 경우 대부분의 수명 동안 나머지 한 명의 명백한 소유자가 있습니다. 이 상황에서는 포인터로 저장된 개체를 '소유'하기 위해 설계된 많은 컬렉션 템플릿을 사용하는 경향이 있습니다. 그것들은 STL 벡터 및 맵 컨테이너로 구현되지만 몇 가지 차이점이 있습니다.

  • 이 모음은 복사하거나 할당 할 수 없습니다. (한 번 객체를 포함합니다.)
  • 객체에 대한 포인터가 삽입됩니다.
  • 콜렉션이 삭제되면 소멸자가 콜렉션의 모든 오브젝트에서 먼저 호출됩니다. (파괴되고 비워지지 않으면 주장하는 다른 버전이 있습니다.)
  • 포인터를 저장하기 때문에 상속 된 객체를 이러한 컨테이너에 저장할 수도 있습니다.

STL에 대한 나의 비애는 그것이 Value 객체에 너무 집중되어 있고 대부분의 응용 프로그램에서 객체는 컨테이너에 사용하기 위해 의미있는 복사 의미가없는 고유 한 엔티티입니다.


10

바, 당신은 어린 아이들과 새로운 쓰레기 수집가들 ...

"소유권"에 대한 매우 강력한 규칙-개체를 삭제할 권한이있는 소프트웨어의 개체 또는 부분 포인터가 "소유"하거나 "단지 보거나 만지지 마십시오"라는 명확한 설명과 현명한 변수 이름 누가 무엇을 소유하는지 결정하려면 모든 서브 루틴 또는 방법 내에서 가능한 한 "샌드위치"패턴을 따르십시오.

create a thing
use that thing
destroy that thing

때로는 광범위하게 다른 장소에서 창조하고 파괴해야 할 때가 있습니다. 나는 그것을 피하기 어렵다고 생각합니다.

복잡한 데이터 구조가 필요한 모든 프로그램에서 "소유자"포인터를 사용하여 다른 객체를 포함하는 객체의 엄격한 명확한 트리를 만듭니다. 이 트리는 응용 프로그램 도메인 개념의 기본 계층을 모델링합니다. 예를 들어 3D 장면은 객체, 조명, 텍스처를 소유합니다. 프로그램이 종료 될 때 렌더링이 끝나면 모든 것을 파괴 할 수있는 명확한 방법이 있습니다.

하나의 엔티티가 다른 엔티티에 액세스해야 할 때마다, 또는 다른 것들을 스캔하기 위해 필요할 때마다 다른 많은 포인터가 정의됩니다. 이것들은 "그냥보고있는"것입니다. 3D 장면 예제의 경우, 오브젝트는 텍스처를 사용하지만 소유하지는 않습니다. 다른 물체도 같은 질감을 사용할 수 있습니다. 오브젝트의 파괴는 텍스처의 파괴를 유발 하지 않습니다 .

예, 시간이 많이 걸리지 만 그게 내가하는 일입니다. 메모리 누수 나 다른 문제는 거의 없습니다. 그러나 저는 고성능 과학, 데이터 수집 및 그래픽 소프트웨어의 제한된 영역에서 일합니다. 나는 종종 은행 및 전자 상거래, 이벤트 중심 GUI 또는 높은 네트워크 비동기 혼란과 같은 거래를 다루지 않습니다. 어쩌면 새로운 방식이 유리할 수도 있습니다!


전적으로 동의합니다. 임베디드 환경에서 작업 할 때 고급 타사 라이브러리가 없을 수도 있습니다.
simon

6
동의하지 않습니다. "그 물건을 사용하십시오"부분에서, 반품이나 예외가 발생하면 거래 취소를 놓치게됩니다. 성능면에서 std :: auto_ptr은 비용이 들지 않습니다. 나는 당신과 같은 방식으로 코딩하지 않습니다. 단지 100 %와 99 % 보안 코드 사이에 차이가 있다는 것입니다. :-)
paercebal

8

좋은 질문입니다!

c ++를 사용하고 있고 게임과 같은 실시간 CPU 및 메모리 보드 응용 프로그램을 개발하는 경우 자체 메모리 관리자를 작성해야합니다.

나는 당신이 더 잘 할 수 있다고 생각합니다. 다양한 작가들의 흥미로운 작품들을 합치면 힌트를 줄 수 있습니다.

  • 고정 크기 할당자는 인터넷 어디에서나 심하게 논의됩니다.

  • Small Object Allocation은 2001 년 Alexandrescu에 의해 그의 완벽한 저서 "Modern c ++ design"에서 소개되었습니다.

  • Dimitar Lazarov가 작성한 "고성능 힙 할당 자"라는 Game Programming Gem 7 (2008)의 놀라운 기사에서 소스 코드를 배포하여 크게 발전했습니다.

  • 기사 에서 훌륭한 리소스 목록을 찾을 수 있습니다

멍청한 쓸모없는 할당자를 직접 작성하지 마십시오 ... 먼저 자신을 문서화하십시오.


5

C ++에서 메모리 관리에 널리 사용되는 기술 중 하나는 RAII 입니다. 기본적으로 생성자 / 소멸자를 사용하여 리소스 할당을 처리합니다. 물론 예외 안전으로 인해 C ++에는 다른 눈에 띄지 않는 세부 사항이 있지만 기본 아이디어는 매우 간단합니다.

이 문제는 일반적으로 소유권 중 하나와 관련이 있습니다. Scott Meyers의 Effective C ++ 시리즈와 Andrei Alexandrescu의 Modern C ++ Design을 읽는 것이 좋습니다.



4

당신이 할 수있는 모든 곳에서 사용자 스마트 포인터! 모든 종류의 메모리 누수가 사라집니다.


4

프로젝트 전체에서 메모리 소유권 규칙을 공유하고 알 수 있습니다. COM 규칙을 사용하면 최상의 일관성을 유지할 수 있습니다 ([in] 매개 변수는 호출자가 소유하고, 수신자는 복사해야합니다. [out] 매개 변수는 호출자가 소유하고, 수신자는 참조를 유지하는 경우 복사해야합니다.)


4

valgrind 는 런타임시 프로그램 메모리 누수를 확인하는 좋은 도구입니다.

대부분의 Linux (Android 포함) 및 Darwin에서 사용할 수 있습니다.

프로그램에 대한 단위 테스트를 작성하는 데 사용하는 경우 테스트에서 체계적으로 실행하는 습관을 가져야합니다. 초기 단계에서 많은 메모리 누수를 방지 할 수 있습니다. 일반적으로 전체 소프트웨어에서 수행하는 간단한 테스트에서이를 쉽게 찾아 낼 수 있습니다.

물론이 조언은 다른 메모리 검사 도구에도 유효합니다.


3

또한 std 라이브러리 클래스 (예 : 벡터)가있는 경우 수동으로 할당 된 메모리를 사용하지 마십시오. 가상 소멸자가 있다는 규칙을 위반했는지 확인하십시오.


2

무언가에 대해 스마트 포인터를 사용할 수 없거나 사용할 수없는 경우 (거대한 붉은 깃발이어야하지만) 다음을 사용하여 코드를 입력하십시오.

allocate
if allocation succeeded:
{ //scope)
     deallocate()
}

명백하지만 범위에 코드를 입력 하기 전에 입력해야합니다.


2

이러한 버그의 빈번한 원인은 개체에 대한 참조 나 포인터를 허용하지만 소유권을 명확하게하지 않는 방법이있는 경우입니다. 스타일과 주석 규칙은이를 덜 가능하게합니다.

함수가 객체의 소유권을 갖는 경우가 특별한 경우가되게하십시오. 이런 상황이 발생하면이를 나타내는 헤더 파일의 함수 옆에 주석을 작성하십시오. 대부분의 경우 객체를 할당하는 모듈이나 클래스가 객체 할당을 취소해야합니다.

const를 사용하면 어떤 경우에는 많은 도움이 될 수 있습니다. 함수가 오브젝트를 수정하지 않고 리턴 후에도 지속되는 참조를 저장하지 않으면 const 참조를 승인하십시오. 호출자의 코드를 읽음으로써 함수가 객체의 소유권을 수락하지 않았 음을 알 수 있습니다. 동일한 함수가 const가 아닌 포인터를 수락하도록 할 수 있으며 호출자는 수신자가 소유권을 수락했다고 가정 할 수도, 아닐 수도 있지만 const 참조로는 의문의 여지가 없습니다.

인수 목록에 상수가 아닌 참조를 사용하지 마십시오. 호출자 코드를 읽을 때 수신자가 매개 변수에 대한 참조를 유지할 수 있는지는 확실하지 않습니다.

참조 카운트 포인터를 권장하는 의견에 동의하지 않습니다. 이것은 일반적으로 잘 작동하지만 버그가 있고 작동하지 않는 경우, 특히 소멸자가 멀티 스레드 프로그램과 같이 사소한 것을 수행하는 경우에 효과적입니다. 너무 세지 않은 경우 참조 카운트가 필요하지 않도록 설계를 확실히 조정하십시오.


2

중요도에 따른 팁 :

-Tip # 1 항상 소멸자를 "가상"으로 선언해야합니다.

-팁 # 2 RAII 사용

-Tip # 3 부스트의 스마트 포인터 사용

-팁 # 4 버그가있는 Smartpointer를 작성하지 말고 boost를 사용하십시오 (현재 프로젝트에서 boost를 사용할 수 없으며 자체 스마트 포인터를 디버깅해야하는데 어려움을 겪었습니다. 같은 경로를 다시 사용하지만 지금은 다시 의존성을 높일 수 없습니다.)

-Tip # 5 캐주얼 / 비 성능이 중요하다면 (수천 개의 오브젝트가있는 게임 에서처럼) Thorsten Ottosen의 부스트 포인터 컨테이너를 살펴보십시오

-Tip # 6 Visual Leak Detection의 "vld"헤더와 같이 선택한 플랫폼에 대한 누출 감지 헤더를 찾으십시오.


트릭이 누락되었지만 '게임'과 '성능이 중요하지 않은'은 어떻게 같은 문장에있을 수 있습니까?
Adam Naylor

게임은 물론 중요한 시나리오의 예입니다. 이 명확하지 못한 수 있음
로버트 굴드에게

팁 # 1은 클래스에 하나 이상의 가상 메소드가있는 경우에만 적용해야합니다. 나는 다형성 상속 트리에서 기본 클래스로 사용되지 않는 클래스에 쓸모없는 가상 소멸자를 강요하지 않습니다.
antred

1

가능하면 boost shared_ptr 및 표준 C ++ auto_ptr을 사용하십시오. 그것들은 소유권 의미를 전달합니다.

auto_ptr을 리턴하면 호출자에게 메모리 소유권을 부여하고 있음을 알리는 것입니다.

shared_ptr을 반환하면 호출자에게 참조가 있고 소유권에 참여한다고 알리는 것은 전적으로 자신의 책임이 아닙니다.

이러한 의미는 매개 변수에도 적용됩니다. 호출자가 auto_ptr을 전달하면 소유권을 부여하는 것입니다.


1

다른 사람들은 스마트 포인터와 같이 처음에 메모리 누수를 피하는 방법을 언급했습니다. 그러나 프로파일 링 및 메모리 분석 도구는 종종 메모리 문제가 발생하면이를 추적 할 수있는 유일한 방법입니다.

Valgrind memcheck 는 훌륭한 무료 프로그램입니다.


1

MSVC의 경우에만 각 .cpp 파일의 맨 위에 다음을 추가하십시오.

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

그런 다음 VS2003 이상으로 디버깅 할 때 프로그램이 종료 될 때 누수에 대해 알려줍니다 (새 / 삭제 추적). 기본이지만 과거에 도움이되었습니다.


1

valgrind (* nix 플랫폼에서만 사용 가능)는 매우 훌륭한 메모리 검사기입니다.


1

메모리를 수동으로 관리하려는 경우 두 가지 경우가 있습니다.

  1. 객체를 만들거나 (아마도 간접적으로 새 객체를 할당하는 함수를 호출하여) 객체를 사용하거나 호출 한 함수에서 사용합니다.
  2. 누군가 나에게 참조를 줬으므로 나는 그것을 풀어서는 안된다.

이러한 규칙 중 하나라도 위반해야하는 경우 문서화하십시오.

포인터 소유권에 관한 것입니다.


1
  • 객체를 동적으로 할당하지 마십시오. 클래스에 적절한 생성자와 소멸자가있는 한, 클래스 유형의 변수를 가리키는 포인터가 아닌 변수를 사용하십시오. 컴파일러가 자동으로 할당하기 때문에 동적 할당 및 할당 해제를 피하십시오.
    실제로 그것은 또한 "스마트 포인터"에 의해 사용되는 메커니즘이며 다른 몇몇 작가들에 의해 RAII로 언급됩니다 ;-).
  • 객체를 다른 함수에 전달할 때는 포인터보다 참조 매개 변수를 선호하십시오. 이것은 몇 가지 가능한 오류를 피합니다.
  • 가능한 경우 매개 변수 const, 특히 객체에 대한 포인터를 선언하십시오. 그렇게하면 객체를 "우연히"해제 할 수 없습니다 (const를 ;-) 던져 버리는 경우 제외).
  • 메모리 할당 및 할당 해제를 수행하는 프로그램의 위치 수를 최소화하십시오. 예 : 동일한 유형을 여러 번 할당하거나 해제하는 경우 해당 함수 (또는 팩토리 메소드 ;-)를 작성하십시오.
    이렇게하면 필요한 경우 디버그 출력 (어드레스 할당 및 할당 해제 등)을 쉽게 만들 수 있습니다.
  • 팩토리 함수를 사용하여 단일 함수에서 여러 관련 클래스의 오브젝트를 할당하십시오.
  • 클래스에 가상 소멸자가있는 공통 기본 클래스가있는 경우 동일한 함수 (또는 정적 메서드)를 사용하여 모든 클래스를 해제 할 수 있습니다.
  • purify (불행히도 $ / € / ...)와 같은 도구로 프로그램을 확인하십시오.

0

메모리 할당 기능을 가로 챌 수 있으며 프로그램 종료시 해제되지 않은 일부 메모리 영역이 있는지 확인할 수 있습니다 ( 모두에 적합하지는 않지만) 응용 프로그램에 ).

또한 연산자 new 및 delete 및 기타 메모리 할당 기능을 대체하여 컴파일 타임에 수행 할 수도 있습니다.

예를 들어이 사이트 에서 확인 하십시오 [C ++에서 메모리 할당 디버깅] 참고 : delete 연산자에는 다음과 같은 트릭도 있습니다.

#define DEBUG_DELETE PrepareDelete(__LINE__,__FILE__); delete
#define delete DEBUG_DELETE

일부 변수에 파일 이름을 저장할 수 있으며 과부하 된 삭제 연산자는 파일이 호출 된 위치를 알 수 있습니다. 이렇게하면 프로그램에서 모든 삭제 및 malloc을 추적 할 수 있습니다. 메모리 검사 순서가 끝나면 할당 된 메모리 블록이 파일 이름과 줄 번호로 식별하여 '삭제되지 않은'메모리 블록을보고 할 수 있어야합니다.

Visual Studio에서 BoundsChecker 와 같은 것을 시도해 볼 수도 있습니다. 매우 흥미롭고 사용하기 쉽습니다.


0

우리는 모든 할당 함수를 앞에 짧은 문자열과 끝에 센티넬 플래그를 추가하는 레이어로 래핑합니다. 예를 들어 "myalloc (pszSomeString, iSize, iAlignment); 또는 new ("description ", iSize) MyObject ();를 호출하면 지정된 크기와 헤더 및 센티넬에 충분한 공간을 내부적으로 할당합니다. 디버그가 아닌 빌드에 대해서는 이것을 언급하는 것을 잊지 마십시오!이 작업을 수행하는 데 약간의 메모리가 더 필요하지만 이점은 비용보다 훨씬 큽니다.

여기에는 세 가지 이점이 있습니다. 먼저 특정 '영역'에 할당 된 코드를 빠르게 검색하여 해당 영역이 해제되어야 할 때 정리하지 않으면 코드 누출을 쉽고 빠르게 추적 할 수 있습니다. 또한 모든 센티넬이 손상되지 않았는지 확인하여 경계를 덮어 쓴 시점을 감지하는 것이 유용 할 수 있습니다. 이로 인해 숨겨져있는 충돌이나 배열 실수를 찾으려고 할 때 많은 시간이 절약되었습니다. 세 번째 장점은 메모리 사용을 추적하여 빅 플레이어가 누구인지 확인하는 것입니다.


0

C ++은 RAII를 염두에두고 설계되었습니다. C ++에서는 메모리를 관리하는 더 좋은 방법이 없다고 생각합니다. 그러나 로컬 범위에 매우 큰 청크 (버퍼 객체와 같은)를 할당하지 않도록주의하십시오. 스택 오버플로가 발생할 수 있으며 해당 청크를 사용하는 동안 범위 검사에 결함이 있으면 다른 변수를 덮어 쓰거나 주소를 반환하여 모든 종류의 보안 허점을 초래할 수 있습니다.


0

다른 장소에서 할당하고 파괴하는 유일한 예 중 하나는 스레드 생성 (전달하는 매개 변수)입니다. 그러나이 경우에도 쉽습니다. 스레드를 생성하는 함수 / 방법은 다음과 같습니다.

struct myparams {
int x;
std::vector<double> z;
}

std::auto_ptr<myparams> param(new myparams(x, ...));
// Release the ownership in case thread creation is successfull
if (0 == pthread_create(&th, NULL, th_func, param.get()) param.release();
...

대신 스레드 함수

extern "C" void* th_func(void* p) {
   try {
       std::auto_ptr<myparams> param((myparams*)p);
       ...
   } catch(...) {
   }
   return 0;
}

꽤 쉽지 않습니까? 스레드 생성이 실패하면 auto_ptr에 의해 리소스가 해제 (삭제)됩니다. 그렇지 않으면 소유권이 스레드로 전달됩니다. 스레드가 너무 빠르면 생성 후 리소스를 해제하기 전에 어떻게해야합니까?

param.release();

주요 기능 / 메소드에서 호출됩니까? 아무것도! 우리는 할당 해제를 무시하기 위해 auto_ptr을 '알아 내기'때문입니다. C ++ 메모리 관리가 쉽지 않습니까? 건배,

에마!


0

다른 리소스 (핸들, 파일, DB 연결, 소켓 등)를 관리하는 것과 같은 방식으로 메모리를 관리합니다. GC는 그들에게 도움이되지 않습니다.


-3

모든 함수에서 정확히 하나의 리턴. 그렇게하면 할당 해제를 할 수 있고 절대로 놓치지 않을 수 있습니다.

그렇지 않으면 실수하기가 너무 쉽습니다.

new a()
if (Bad()) {delete a; return;}
new b()
if (Bad()) {delete a; delete b; return;}
... // etc.

귀하의 답변이 예제 코드와 일치하지 않습니까? 나는 "단일 리턴"이라는 대답에 동의하지만 예제 코드는하지 말아야 할 것을 보여줍니다.
simon

1
C ++ RAII의 요점은 정확하게 작성한 코드를 피하는 것입니다. C에서 이것은 아마도 옳은 일입니다. 그러나 C ++에서는 코드에 결함이 있습니다. 예를 들어 : new b ()가 발생하면 어떻게됩니까? 당신은 누출.
paercebal
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.