IT박스

포인터를 삭제 한 후 포인터를 NULL로 만드는 것이 좋습니다.

itboxs 2020. 6. 23. 07:58
반응형

포인터를 삭제 한 후 포인터를 NULL로 만드는 것이 좋습니다.


먼저 말하고 스마트 포인터를 사용하면 걱정할 필요가 없습니다.

다음 코드의 문제점은 무엇입니까?

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

이것은 다른 질문 대한 답변과 의견으로 시작 되었습니다. Neil Butterworth의 한 의견은 몇 가지 공감대를 만들었습니다.

삭제 후 포인터를 NULL로 설정하는 것은 C ++에서 보편적 인 모범 사례가 아닙니다. 좋은 일이 있고 무의미하고 오류를 숨길 수있는 경우가 있습니다.

도움이되지 않는 상황이 많이 있습니다. 그러나 내 경험으로는 아프지 않습니다. 누군가 나를 밝힙니다.


포인터를 0으로 설정하면 (표준 C ++에서 "널 (null)"임, C에서 NULL로 정의한 NULL이 약간 다름) 이중 삭제시 충돌이 발생하지 않습니다.

다음을 고려하세요:

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공유 소유권 또는 다른 스마트 포인터 구현을 살펴 보는 것이 좋습니다 .


확실히 지적한 것을 삭제 한 후 포인터를 NULL로 설정하면 아프지 않지만보다 근본적인 문제에 대해 약간의 반창고입니다. 왜 포인터를 먼저 사용합니까? 두 가지 일반적인 이유를 볼 수 있습니다.

  • 단순히 힙에 할당 된 것을 원했습니다. 이 경우 RAII 객체로 감싸는 것이 훨씬 안전하고 깨끗했을 것입니다. 더 이상 객체가 필요하지 않으면 RAII 객체의 범위를 종료하십시오. 그것이 std::vector작동 하는 방식이며, 할당 해제 된 메모리에 포인터를 실수로 남겨 두는 문제를 해결합니다. 포인터가 없습니다.
  • 또는 복잡한 공유 소유권 의미론을 원했을 수도 있습니다. 에서 반환 된 포인터 는 호출 된 포인터 new와 같지 않을 수 있습니다 delete. 그 동안 여러 객체가 동시에 객체를 사용했을 수 있습니다. 이 경우 공유 포인터 또는 유사한 것이 바람직했을 것입니다.

제 경험에 따르면 사용자 코드에 포인터를두면 잘못하고 있습니다. 처음에는 쓰레기를 가리 키도록 포인터가 없어야합니다. 왜 그 유효성을 보장해야하는 책임이 없는가? 뾰족한 물체가 스코프를 끝낼 때 왜 그 범위가 끝나지 않습니까?


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

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

나는 그것이 가리키는 객체를 삭제 한 후에 항상 포인터를 NULL(now nullptr) 로 설정했습니다 .

  1. 해제 된 메모리에 대한 많은 참조를 포착하는 데 도움이 될 수 있습니다 (널 포인터를 무시하여 플랫폼 결함을 가정).

  2. 예를 들어 포인터의 복사본이있는 경우 사용 가능한 메모리에 대한 모든 참조를 포착하지는 않습니다. 그러나 일부는 없음보다 낫습니다.

  3. 이중 삭제를 숨기지 만 이미 사용 가능한 메모리에 액세스하는 것보다 훨씬 덜 일반적입니다.

  4. 대부분의 경우 컴파일러는이를 최적화하려고합니다. 따라서 그것이 불필요하다는 주장은 설득력이 없습니다.

  5. 이미 RAII를 사용하고 있다면 delete코드에 s 가 많지 않으므로 추가 할당으로 인해 혼란이 발생한다는 주장은 설득력이 없습니다.

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

  7. 그래도 문제가 해결되지 않으면 대신 스마트 포인터 또는 참조를 사용하십시오.

또한 리소스가 해제 될 때 (일반적으로 리소스를 캡슐화하기 위해 작성된 RAII 래퍼의 소멸자에만 있음) 다른 유형의 리소스 핸들을 리소스 없음 값으로 설정합니다.

나는 큰 (9 백만 개의 진술) 상용 제품 (주로 C)을 작업했다. 어느 시점에서, 우리는 메모리가 해제 될 때 매크로 매직을 사용하여 포인터를 널 아웃했습니다. 이것은 즉시 수정 된 많은 숨어있는 버그를 즉시 노출 시켰습니다. 내가 기억할 수있는 한, 더블 프리 버그는 없었습니다.

업데이트 : Microsoft는 이것이 보안을위한 좋은 방법이라고 믿고 SDL 정책에 권장합니다. / SDL 옵션을 사용하여 컴파일하면 MSVC ++ 11은 삭제 된 포인터를 자동으로 (많은 상황에서) 스톰 핑합니다 .


먼저,이 주제와 밀접한 관련 주제에 대한 기존의 질문이 많이 있습니다 . .

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

Foo * p2 = p;

그런 다음 p2를 걱정으로 설정하면 p를 NULL로 설정하면 거의 달성되지 않습니다.

이것은 포인터를 NULL로 설정하는 것이 항상 의미가 없다고 말하는 것은 아닙니다. 예를 들어, p가 수명이 p를 포함하는 클래스와 정확히 같지 않은 자원을 가리키는 멤버 변수 인 경우 p를 NULL로 설정하면 자원의 유무를 나타내는 유용한 방법이 될 수 있습니다.


뒤에 더 많은 코드가 있으면 delete예. 생성자에서 또는 메소드 나 함수의 끝에서 포인터가 삭제되면 아니오.

The point of this parable is to remind the programmer, during run-time, that the object has already been deleted.

An even better practice is to use Smart Pointers (shared or scoped) which automagically delete their target objects.


As others have said, delete ptr; ptr = 0; is not going to cause demons to fly out of your nose. However, it does encourage the usage of ptr as a flag of sorts. The code becomes littered with delete and setting the pointer to NULL. The next step is to scatter if (arg == NULL) return; through your code to protect against the accidental usage of a NULL pointer. The problem occurs once the checks against NULL become your primary means of checking for the state of an object or program.

I'm sure that there is a code smell about using a pointer as a flag somewhere but I haven't found one.


I'll change your question slightly:

Would you use an uninitialized pointer? You know, one that you didn't set to NULL or allocate the memory it points to?

There are two scenarios where setting the pointer to NULL can be skipped:

  • the pointer variable goes out of scope immediately
  • you have overloaded the semantic of the pointer and are using its value not only as a memory pointer, but also as a key or raw value. this approach however suffers from other problems.

Meanwhile, arguing that setting the pointer to NULL might hide errors to me sounds like arguing that you shouldn't fix a bug because the fix might hide another bug. The only bugs that might show if the pointer is not set to NULL would be the ones that try to use the pointer. But setting it to NULL would actually cause exactly the same bug as would show if you use it with freed memory, wouldn't it?


If you have no other constraint that forces you to either set or not set the pointer to NULL after you delete it (one such constraint was mentioned by Neil Butterworth), then my personal preference is to leave it be.

For me, the question isn't "is this a good idea?" but "what behavior would I prevent or allow to succeed by doing this?" For example, if this allows other code to see that the pointer is no longer available, why is other code even attempting to look at freed pointers after they are freed? Usually, it's a bug.

It also does more work than necessary as well as hindering post-mortem debugging. The less you touch memory after you don't need it, the easier it is to figure out why something crashed. Many times I have relied on the fact that memory is in a similar state to when a particular bug occurred to diagnose and fix said bug.


Explicitly nulling after delete strongly suggests to a reader that the pointer represents something which is conceptually optional. If I saw that being done, I'd start worrying that everywhere in the source the pointer gets used that it should be first tested against NULL.

If that's what you actually mean, it's better to make that explicit in the source using something like boost::optional

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

But if you really wanted people to know the pointer has "gone bad", I'll pitch in 100% agreement with those who say the best thing to do is make it go out of scope. Then you're using the compiler to prevent the possibility of bad dereferences at runtime.

That's the baby in all the C++ bathwater, shouldn't throw it out. :)


In a well structured program with appropriate error checking, there is no reason not to assign it null. 0 stands alone as a universally recognized invalid value in this context. Fail hard and Fail soon.

Many of the arguments against assigning 0 suggest that it could hide a bug or complicate control flow. Fundamentally, that is either an upstream error (not your fault (sorry for the bad pun)) or another error on the programmer's behalf -- perhaps even an indication that program flow has grown too complex.

If the programmer wants to introduce the use of a pointer which may be null as a special value and write all the necessary dodging around that, that's a complication they have deliberately introduced. The better the quarantine, the sooner you find cases of misuse, and the less they are able to spread into other programs.

Well structured programs may be designed using C++ features to avoid these cases. You can use references, or you can just say "passing/using null or invalid arguments is an error" -- an approach which is equally applicable to containers, such as smart pointers. Increasing consistent and correct behavior forbids these bugs from getting far.

From there, you have only a very limited scope and context where a null pointer may exist (or is permitted).

The same may be applied to pointers which are not const. Following the value of a pointer is trivial because its scope is so small, and improper use is checked and well defined. If your toolset and engineers cannot follow the program following a quick read or there is inappropriate error checking or inconsistent/lenient program flow, you have other, bigger problems.

Finally, your compiler and environment likely has some guards for the times when you would like to introduce errors (scribbling), detect accesses to freed memory, and catch other related UB. You can also introduce similar diagnostics into your programs, often without affecting existing programs.


Let me expand what you've already put into your question.

Here's what you've put into your question, in bullet-point form:


Setting pointers to NULL following delete is not universal good practice in C++. There are times when:

  • it is a good thing to do
  • and times when it is pointless and can hide errors.

However, there is no times when this is bad! You will not introduce more bugs by explicitly nulling it, you will not leak memory, you will not cause undefined behaviour to happen.

So, if in doubt, just null it.

Having said that, if you feel that you have to explicitly null some pointer, then to me this sounds like you haven't split up a method enough, and should look at the refactoring approach called "Extract method" to split up the method into separate parts.


Yes.

The only "harm" it can do is to introduce inefficiency (an unnecessary store operation) into your program - but this overhead will be insignificant in relation to the cost of allocating and freeing the block of memory in most cases.

If you don't do it, you will have some nasty pointer derefernce bugs one day.

I always use a macro for delete:

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

(and similar for an array, free(), releasing handles)

You can also write "self delete" methods that take a reference to the calling code's pointer, so they force the calling code's pointer to NULL. For example, to delete a subtree of many objects:

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;
}

edit

Yes, these techniques do violate some rules about use of macros (and yes, these days you could probably achieve the same result with templates) - but by using over many years I never ever accessed dead memory - one of the nastiest and most difficult and most time consuming to debug problems you can face. In practice over many years they have effectively eliminated a whjole class of bugs from every team I have introduced them on.

There are also many ways you could implement the above - I am just trying to illustrate the idea of forcing people to NULL a pointer if they delete an object, rather than providing a means for them to release the memory that does not NULL the caller's pointer.

Of course, the above example is just a step towards an auto-pointer. Which I didn't suggest because the OP was specifically asking about the case of not using an auto pointer.


"There are times when it is a good thing to do, and times when it is pointless and can hide errors"

I can see two problems: That simple code:

delete myObj;
myobj = 0

becomes to a for-liner in multithreaded environment:

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

The "best practice" of Don Neufeld don't apply always. E.g. in one automotive project we had to set pointers to 0 even in destructors. I can imagine in safety-critical software such rules are not uncommon. It is easier (and wise) to follow them than trying to persuade the team/code-checker for each pointer use in code, that a line nulling this pointer is redundant.

Another danger is relying on this technique in exceptions-using code:

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

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

In such code either you produce resource-leak and postpone the problem or the process crashes.

So, this two problems going spontaneously through my head (Herb Sutter would for sure tell more) make for me all the questions of the kind "How to avoid using smart-pointers and do the job safely with normal pointers" as obsolete.


There is always Dangling Pointers to worry about.


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.

참고URL : https://stackoverflow.com/questions/1931126/is-it-good-practice-to-null-a-pointer-after-deleting-it

반응형