문제

나를 시작하게 말해서, 스마트 포인터 당신에 대해 걱정할 필요가 없다.

은 무엇이 문제는 다음과 같은 코드?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

이에 의해 촉발 답글 다른 질문입니다.One comment 서 닐 버 생성된 약간 늦게 집에 가서 만들:

설정 포인터가 NULL 로 다음과 같은 삭제되지 않은 보편적인 좋은 연습에서는 C++.시간이 있다면 그것은 좋은 일을 하고,그 때 그것은 무의미할 수 있습 숨기기 오류가 있습니다.

거기에 많은 경우가 그것이 없을 돕습니다.하지만 내 경험에 의하면,그 수를 요청할 수 있습니다.누군가가 나를 계몽.

도움이 되었습니까?

해결책

표준 C ++에서 "NULL"인 포인터를 설정하면 C의 널 정의는 다소 다릅니다)는 이중 삭제에서 충돌을 피합니다.

다음을 고려하세요:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

반면:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

다시 말해, 삭제 된 포인터를 0으로 설정하지 않으면 이중 삭제를하는 경우 문제가 발생합니다. 삭제 후 포인터를 0으로 설정하는 것에 대한 인수는 이렇게하는 것이 더블 삭제 버그를 마스크하고 도둑질하지 않는 것입니다.

분명히 이중 삭제 버그가 없지만 소유권 시맨틱과 객체 수명에 따라 실제로 달성하기가 어려울 수 있습니다. UB보다 마스크 된 이중 삭제 버그를 선호합니다.

마지막으로, 객체 할당 관리와 관련된 사이드 인트를 살펴 보는 것이 좋습니다. std::unique_ptr 엄격한/단수 소유권을 위해 std::shared_ptr 귀하의 요구에 따라 공유 소유권 또는 다른 스마트 포인터 구현.

다른 팁

확실히 아플 수 없다고 지적한 것을 삭제 한 후 포인터를 널로 설정했지만, 더 근본적인 문제에 대해 종종 약간의 반창고입니다. 왜 처음에는 포인터를 사용하고 있습니까? 두 가지 전형적인 이유를 볼 수 있습니다.

  • 당신은 단순히 힙에 할당 된 것을 원했습니다. 이 경우 Raii 객체로 포장하는 것은 훨씬 더 안전하고 깨끗했을 것입니다. 더 이상 물체가 필요하지 않을 때 Raii 객체의 범위를 종료하십시오. 그게 방법입니다 std::vector 작동하며 실수로 포인터를 거래 한 메모리에 남겨 두는 문제를 해결합니다. 포인터가 없습니다.
  • 또는 아마도 당신은 복잡한 공유 소유권 의미를 원했을 것입니다. 포인터가 돌아 왔습니다 new 그와 같지 않을 수 있습니다 delete 호출됩니다. 여러 객체가 그 동안 객체를 동시에 사용했을 수 있습니다. 이 경우 공유 포인터 또는 이와 유사한 것이 바람직했을 것입니다.

내 규칙은 사용자 코드로 포인터를 남겨두면 잘못하고 있다는 것입니다. 포인터는 처음에 쓰레기를 가리키기 위해 거기에있어서는 안됩니다. 왜 대상이 유효성을 보장하는 책임이 없는가? 포인트-투 객체가 할 때 그 범위가 끝나지 않는 이유는 무엇입니까?

더 나은 모범 사례가 있습니다. 가능하면 변수의 범위를 끝내십시오!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}

나는 항상 포인터를 설정했습니다 NULL (지금 nullptr) 객체를 삭제 한 후 가리 킵니다.

  1. 그것은 자유 메모리에 대한 많은 참조를 포착하는 데 도움이 될 수 있습니다 (널 포인터의 deref에서 플랫폼 결함을 가정 할 때).

  2. 예를 들어 포인터의 사본이있는 경우 무료 메모리에 대한 모든 참조를 포착하지는 않습니다. 그러나 일부는 아무것도 아닌 것보다 낫습니다.

  3. 그것은 이중 지색을 가릴 것이지만, 이미 해방 된 메모리에 액세스하는 것보다 훨씬 덜 일반적입니다.

  4. 많은 경우에 컴파일러는 그것을 최적화 할 것입니다. 따라서 불필요하다는 주장은 나를 설득하지 않습니다.

  5. 이미 Raii를 사용하고 있다면 많지 않습니다. delete당신의 코드에서 우선적으로, 추가 할당이 혼란을 일으킨다는 주장은 나를 설득하지 않습니다.

  6. 디버깅 할 때 오래된 포인터보다는 널 값을 보는 것이 종종 편리합니다.

  7. 이것이 여전히 당신을 귀찮게한다면, 대신 스마트 포인터 나 참조를 사용하십시오.

또한 리소스가 무료 일 때 (일반적으로 리소스를 캡슐화하도록 작성된 RAII 래퍼의 파괴자에만) 다른 유형의 리소스 핸들을 재료가없는 값으로 설정했습니다.

나는 대규모 (9 백만 명령문) 상용 제품 (주로 C)에서 일했습니다. 어느 시점에서, 우리는 메모리가 해제 될 때 포인터를 무효화하기 위해 매크로 마법을 사용했습니다. 이것은 즉시 즉시 고정 된 많은 숨어있는 버그를 노출시켰다. 내가 기억할 수있는 한, 우리는 두 배의 버그가 없었습니다.

업데이트: Microsoft는 이것이 보안을위한 우수한 관행이라고 생각하며 SDL 정책에서 관행을 추천합니다. 분명히 MSVC ++ 11 삭제 된 포인터를 밟습니다 /SDL 옵션을 컴파일하는 경우 자동으로 (많은 상황에서)

첫째, 예를 들어이 주제와 밀접하게 관련된 주제에 대한 기존 질문이 많이 있습니다. 삭제가 포인터를 NULL로 설정하지 않는 이유는 무엇입니까?.

코드에서 발생하는 문제 (사용 P). 예를 들어, 어딘가에 다음과 같은 코드가있는 경우 다음과 같습니다.

Foo * p2 = p;

그런 다음 P2를 걱정할 포인터 P2가 있기 때문에 P를 NULL로 설정하면 거의 성취되지 않습니다.

이것은 널로 포인터를 설정하는 것이 항상 무의미하다고 말하는 것은 아닙니다. 예를 들어, P가 P를 포함하는 클래스와 정확히 동일하지 않은 자원을 가리키는 회원 변수 인 경우 P를 NULL로 설정하는 것이 자원의 존재 또는 부재를 나타내는 유용한 방법 일 수 있습니다.

이후에 더 많은 코드가있는 경우 delete, 예. 포인터가 생성자 또는 메소드 또는 기능의 끝에서 삭제 된 경우.

이 비유의 요점은 런타임 중에 프로그래머에게 객체가 이미 삭제되었음을 상기시키는 것입니다.

더 나은 연습은 대상 객체를 자동으로 삭제하는 스마트 포인터 (공유 또는 범위)를 사용하는 것입니다.

다른 사람들이 말했듯이 delete ptr; ptr = 0; 악마가 코에서 날아 가지 않을 것입니다. 그러나 그것은 사용을 장려합니다 ptr 일종의 깃발로. 코드가 흩어집니다 delete 포인터를 설정합니다 NULL. 다음 단계는 산란하는 것입니다 if (arg == NULL) return; 우발적 인 사용을 방지하기 위해 코드를 통해 NULL 바늘. 문제는 확인되면 문제가 발생합니다 NULL 객체 또는 프로그램의 상태를 확인하는 주요 수단이 되십시오.

포인터를 어딘가에 깃발로 사용하는 것에 대한 코드 냄새가 있다고 확신하지만 찾지 못했습니다.

질문을 약간 변경하겠습니다.

비 초기의 포인터를 사용 하시겠습니까? 당신은 당신이 가리키는 메모리를 null로 설정하지 않았다는 것을 알고 있습니까?

포인터를 NULL로 설정할 수있는 두 가지 시나리오가 있습니다.

  • 포인터 변수는 즉시 범위를 벗어납니다
  • 당신은 포인터의 의미를 과부하시키고 그 값을 메모리 포인터뿐만 아니라 키 또는 원시 값으로 사용하고 있습니다. 그러나이 접근법은 다른 문제로 고통 받고 있습니다.

한편, 포인터를 NULL로 설정하면 오류를 숨길 수 있다고 주장하면 수정이 다른 버그를 숨길 수 있기 때문에 버그를 고치지 않아야한다고 주장하는 것처럼 들릴 수 있습니다. 포인터가 널로 설정되지 않았는지 보여줄 수있는 유일한 버그는 포인터를 사용하려고하는 버그입니다. 그러나 NULL로 설정하면 실제로 메모리가 해방 된 메모리와 함께 사용하면 표시되는 것과 정확히 동일한 버그가 발생할 수 있습니까?

삭제 한 후 포인터를 NULL로 설정하거나 설정하지 않도록하는 다른 제약 조건이 없다면 닐 버터 워스), 그러면 내 개인적인 취향은 그것을 떠나는 것입니다.

나에게 질문은 "이것이 좋은 생각입니까?" 그러나 "이 작업을 수행함으로써 어떤 행동을 방해하거나 성공할 수 있습니까?" 예를 들어, 다른 코드가 포인터를 더 이상 사용할 수 없음을 확인할 수있게되면 다른 코드가 해방 된 후 해방 된 포인터를 보려고 시도하는 이유는 무엇입니까? 일반적으로 버그입니다.

또한 사후 디버깅을 방해 할뿐만 아니라 필요한 것보다 더 많은 작업을 수행합니다. 필요하지 않은 후에는 메모리를 만질수록 무언가가 추락 한 이유를 쉽게 파악할 수 있습니다. 여러 번 나는 메모리가 특정 버그가 발생했을 때와 비슷한 상태에 있다는 사실에 의존했다.

삭제 후 명시 적으로 무효화는 독자에게 포인터가 개념적으로 무언가를 나타내는 것을 독자에게 제안합니다. 선택 과목. 내가 그 일이 끝나는 것을 보았을 때, 나는 소스의 모든 곳에서 포인터가 사용되는 것을 걱정할 것이라는 걱정을 시작합니다.

그것이 당신이 실제로 의미하는 바이라면, 같은 것을 사용하여 소스에서 그 명시 적으로 만드는 것이 좋습니다. 부스트 :: 선택 사항

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

그러나 사람들이 포인터가 "나쁘다"는 것을 알기를 원한다면, 나는 최선을 다하는 사람들과 100% 계약을 맺을 것입니다. 그런 다음 컴파일러를 사용하여 런타임에서 불량이 발생할 가능성을 방지합니다.

그것은 모든 C ++ 목욕물에있는 아기입니다. 그것을 버리지 말아야합니다. :)

적절한 오류 확인이있는 잘 구조화 된 프로그램에는 이유가 없습니다. ~ 아니다 널 할당하려면. 0 이 맥락에서 보편적으로 인정 된 유효하지 않은 가치 로서만 서 있습니다. 실패하고 곧 실패합니다.

할당에 대한 많은 주장 0 그것을 제안하십시오 ~할 수 있었다 버그를 숨기거나 통제 흐름을 복잡하게하십시오. 기본적으로, 그것은 상류 오류 (당신의 결함이 아님) 또는 프로그래머를 대신하여 다른 오류입니다. 아마도 프로그램 흐름이 너무 복잡해 졌다는 것을 나타냅니다.

프로그래머가 특별한 값으로 무효화 할 수있는 포인터의 사용을 소개하고 그 주위에 필요한 모든 회피를 작성하려면, 의도적으로 소개 한 합병증입니다. 검역소가 더 좋을수록 오용의 경우를 빨리 알게되고 다른 프로그램으로 전파 될 수 있습니다.

잘 구조화 된 프로그램은 이러한 경우를 피하기 위해 C ++ 기능을 사용하여 설계 될 수 있습니다. 참조를 사용하거나 "NULL 또는 유효하지 않은 인수를 사용하는 것은 오류입니다"라고 말할 수 있습니다. 스마트 포인터와 같은 컨테이너와 동일하게 적용되는 접근법입니다. 일관되고 올바른 행동을 증가시키는 것은 이러한 버그가 멀어지는 것을 금지합니다.

거기에서 당신은 널 포인터가 존재할 수있는 매우 제한된 범위와 컨텍스트 만 있습니다 (또는 허용).

동일하지 않은 포인터에도 동일하게 적용될 수 있습니다. const. 포인터의 값을 따르는 것은 범위가 너무 작고 부적절한 사용을 확인하고 잘 정의하기 때문에 사소합니다. 툴셋과 엔지니어가 빠른 읽기 후 프로그램을 따르지 않거나 부적절한 오류 확인 또는 일관성없는/관대 한 프로그램 흐름이있는 경우 다른 더 큰 문제가 있습니다.

마지막으로, 컴파일러와 환경에는 오류 (스크립 블링)를 도입하고 자유 메모리에 대한 액세스를 감지하고 다른 관련 UB를 잡으려는 시대에 대한 경비원이있을 수 있습니다. 또한 기존 프로그램에 영향을 미치지 않으면 서 프로그램에 유사한 진단을 도입 할 수도 있습니다.

당신이 이미 당신의 질문에 넣은 것을 확장하겠습니다.

다음은 총알 포인트 형식으로 질문에 넣은 것입니다.


삭제 다음 삭제 다음에 포인터를 설정하는 것은 C ++에서 보편적 인 모범 사례가 아닙니다. 다음과 같은 시간이 있습니다.

  • 하는 것은 좋은 일입니다
  • 무의미하고 오류를 숨길 수있는 시간.

그러나 있습니다 시간이 없습니다 이렇게 할 때 나쁜! 당신은 할 것입니다 ~ 아니다 명시 적으로 널리킹하여 더 많은 버그를 도입하면 새다 기억, 당신은하지 않을 것입니다 정의되지 않은 행동을 일으킨다 일어나기 위해.

의심스러운 경우 그냥 무효화하십시오.

포인터를 명시 적으로 무효화해야한다고 생각한다면, 나에게 이것은 메소드를 충분히 나누지 않은 것처럼 들리고, "추출 방법"이라는 리팩토링 접근법을보고 메소드를 분할해야합니다. 별도의 부품.

그렇습니다.

다만"해가"그것을 할 수 있습을 소개하는 것입 비효율성(불필요한 저장 작업)을 프로그램을-하지만이 오버헤드가 될 것이 중요하지 관하여는 비용 할당 및 해제 블록의 메모리는 대부분의 경우에.

하지 않으면 그것은,당신은 당신 몇 가지 불쾌한 포인터 derefernce 버그에 하나의 날입니다.

제가 항상 매크로를 사용에 대한 삭제:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(과 비슷한 시설,무료()발표,손잡이)

를 작성할 수도 있습니다"self"삭제하는 방법을 참조 코드의 포인터 내가 더 먹고 더 많이 자라고 그걸 내게 호출하는 코드의 포인터 NULL 입니다.예를 들어,삭제를 하위 트리의 많은 개체:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

편집

예,이러한 기술 수행을 위반하는 일부에 대한 규칙을 사용하는 매크로(예,요즘 당신은 아마도 동일한 결과를 얻을 수 있습니다와 함께 템플릿)-그러나 사용하여 많은 년 이상 나 액세스 죽은 메모리의 가장 쉽고 어렵고 가장 시간이 많이 소요되는 문제를 디버깅할 수 있는 얼굴입니다.실제로 많은 년간 그들이 효과적으로 제거 whjole 클래스의 버그에서 모든 팀을 도입했습니다.

또한 많은 방법으로 구현할 수 있는 위에 나는 그냥을 설명하려고 생각의 사람들을 강제 NULL 포인터를 삭제하는 경우 그 개체보다는 방법을 제공하면 그들을 위한 메모리를 해제하지 않는 NULL 을 발신자의 포인터이다.

물론,위의 예에서는 단지 단계는 자동 포인터이다.나는 것이 좋기 때문에 OP 구체적으로 묻는 것에 대한 사용하지 않는 경우는 자동 포인터이다.

"좋은 일이 될 때가 있고 무의미하고 오류를 숨길 수있는 시간이 있습니다."

두 가지 문제를 볼 수 있습니다 : 간단한 코드 :

delete myObj;
myobj = 0

멀티 스레드 환경에서 라이너가됩니다.

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

Don Neufeld의 "모범 사례"는 항상 적용되지 않습니다. 예를 들어, 하나의 자동차 프로젝트에서 우리는 파괴자에서도 포인터를 0으로 설정해야했습니다. 안전한 소프트웨어에서 그러한 규칙이 드문 일이 아니라고 상상할 수 있습니다. 코드에서 각 포인터 사용에 대해 팀/코드-체커를 설득하려고하는 것 보다이 포인터를 무효화하는 라인이 중복되는 것보다 더 쉽고 현명한 것입니다.

또 다른 위험은 예외 사용 코드 에서이 기술에 의존하는 것입니다.

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

이러한 코드에서는 자원 누출을 생성하고 문제를 연기하거나 프로세스 충돌이 발생합니다.

따라서이 두 가지 문제는 내 머리를 통해 자발적으로 진행됩니다 (Herb Sutter는 더 많은 것을 말해 줄 것입니다)은 "스마트 포인터 사용을 피하고 일반적인 포인터로 안전하게 작업을 수행하는 방법"의 모든 질문을 쓸모 없게 만듭니다.

항상 있습니다 매달려 포인터 에 대해서 걱정하다.

If you're going to reallocate the pointer before using it again (dereferencing it, passing it to a function, etc.), making the pointer NULL is just an extra operation. However, if you aren't sure whether it will be reallocated or not before it is used again, setting it to NULL is a good idea.

As many have said, it is of course much easier to just use smart pointers.

Edit: As Thomas Matthews said in this earlier answer, if a pointer is deleted in a destructor, there isn't any need to assign NULL to it since it won't be used again because the object is being destroyed already.

I can imagine setting a pointer to NULL after deleting it being useful in rare cases where there is a legitimate scenario of reusing it in a single function (or object). Otherwise it makes no sense - a pointer needs to point to something meaningful as long as it exists - period.

If the code does not belong to the most performance-critical part of your application, keep it simple and use a shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

It performs reference counting and is thread-safe. You can find it in the tr1 (std::tr1 namespace, #include < memory >) or if your compiler does not provide it, get it from boost.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top