해제 후 변수를 NULL로 설정


156

우리 회사에는 메모리를 비운 후 변수를로 재설정하는 코딩 규칙이 NULL있습니다. 예를 들어 ...

void some_func () 
{
    int *nPtr;

    nPtr = malloc (100);

    free (nPtr);
    nPtr = NULL;

    return;
}

위에 표시된 코드와 같은 경우로 설정하면 NULL아무런 의미가 없다고 생각합니다. 아니면 뭔가 빠졌습니까?

그러한 경우에 의미가 없다면, "품질 팀"에게 맡겨서이 코딩 규칙을 제거 할 것입니다. 조언을 부탁드립니다.


2
ptr == NULL무언가를하기 전에 점검 할 수 있으면 항상 유용 합니다. free'd 포인터를 무효화하지 않으면 ptr != NULL여전히 사용할 수없는 포인터를 얻습니다 .
Ki Jéy

매달려있는 포인터 Use-After-Free 와 같은 악용 가능한 취약점으로 이어질 수 있습니다.
Константин Ван

답변:


285

사용하지 않는 포인터를 NULL로 설정하는 것은 방어적인 스타일로, 매달려있는 포인터 버그를 방지합니다. 댕글 링 포인터가 해제 된 후 액세스되면 임의 메모리를 읽거나 덮어 쓸 수 있습니다. 널 포인터에 액세스하면 대부분의 시스템에서 즉시 충돌이 발생하여 오류가 무엇인지 즉시 알려줍니다.

지역 변수의 경우, 포인터가 해제 된 후 더 이상 포인터에 액세스하지 않는 것이 "분명한"경우에는 의미가 없습니다. 따라서이 스타일은 멤버 데이터 및 전역 변수에 더 적합합니다. 로컬 변수의 경우에도 메모리가 해제 된 후에 함수가 계속되면 좋은 접근 방법 일 수 있습니다.

스타일을 완성하려면 포인터에 실제 포인터 값이 할당되기 전에 포인터를 NULL로 초기화해야합니다.


3
"진정한 포인터 값이 할당되기 전에 포인터를 NULL로 초기화하는"이유를 이해하지 못합니까?
Paul Biggar

26
@Paul : 특정한 경우, 선언은을 읽을 수 int *nPtr=NULL;있습니다. 이제 다음 줄에서 malloc이 바로 뒤 따르면서 이것이 중복되는 것에 동의합니다. 그러나 선언과 첫 번째 초기화 사이에 코드가 있으면 아직 값이 없어도 변수 사용을 시작할 수 있습니다. null을 초기화하면 segfault를 얻습니다. 그렇지 않으면 임의 메모리를 다시 읽거나 쓸 수 있습니다. 마찬가지로, 나중에 변수가 조건부로만 초기화되면 나중에 잘못된 액세스로 인해 null 초기화를 기억하면 즉시 충돌이 발생합니다.
Martin v. Löwis

1
필자는 개인적으로 소위 코드베이스에서 null을 참조 해제하는 데 오류가 발생하면 소유하지 않은 주소를 참조 해제하는 데 오류가 발생하는 것처럼 모호하다고 생각합니다. 나는 개인적으로 귀찮게하지 않습니다.
wilhelmtell

9
Wilhelm, 요점은 널 포인터 역 참조로 결정적인 충돌과 문제의 실제 위치를 얻는다는 것입니다. 액세스가 잘못되면 예기치 않은 장소에서 예기치 않은 방식으로 데이터 나 동작이 손상 될 수 있습니다.
Amit Naidu

4
실제로 포인터를 NULL로 초기화하면 적어도 하나의 중요한 단점이 있습니다. 초기화되지 않은 변수에 대해 컴파일러가 경고하지 못하게 할 수 있습니다. 코드의 논리가 실제로 포인터에 대한 해당 값을 명시 적으로 처리하지 않는 한 (즉, (nPtr == NULL) 일을하는 경우 ...) 그대로 두는 것이 좋습니다.
Eric

37

NULLafter에 대한 포인터를 설정하는 free것은 특허 적으로 허위 전제에 대한 "좋은 프로그래밍"규칙으로 대중화되는 모호한 관행입니다. 그것은 "올바른 소리"범주에 속하는 가짜 진실 중 하나이지만 실제로는 아무 것도 유용하지 않습니다 (때로는 부정적인 결과를 초래합니다).

주장에 따르면, 포인터를 NULLafter로 설정 free하면 동일한 포인터 값이 free두 번 이상 전달 될 때 두려운 "더블 프리"문제를 방지 할 수 있습니다. 그러나 실제로는 10 개 중 9 개의 경우 에 동일한 포인터 값을 가진 다른 포인터 객체가에 대한 인수로 사용될 때 실제 "double free"문제가 발생합니다 free. 말할 필요도없이, NULLafter에 대한 포인터를 설정하면 free이러한 경우 문제를 방지 할 수있는 것은 없습니다.

물론에 대한 포인터와 동일한 포인터 객체를 사용할 때 "double free"문제가 발생할 수 free있습니다. 그러나 실제로는 이와 같은 상황에서는 일반적으로 코드의 일반적인 논리적 구조에 문제가있는 것이지 우연히 "더블 프리"가 아닙니다. 이러한 경우 문제를 처리하는 올바른 방법은 동일한 포인터가 free두 번 이상 전달 될 때 상황을 피하기 위해 코드 구조를 검토하고 다시 생각하는 것입니다. 이러한 경우 포인터를 설정하고 NULL문제를 "고정"한 것으로 간주하는 것은 카펫 아래에서 문제를 해결하려는 시도에 지나지 않습니다. 코드 구조에 문제가 있으면 항상 다른 방법을 찾을 수 있기 때문에 일반적인 경우에는 효과가 없습니다.

마지막으로, 코드가 포인터 값에 의존 NULL하지 않도록 특별히 설계된 NULL경우 포인터 값을 NULLafter 로 설정하는 것이 free좋습니다. 그러나 일반적인 "모범 사례"규칙 ( "항상 포인터를 " NULL뒤에 표시 free"에서와 같이)은 잘 알려져 있고 쓸모없는 가짜이며, 순전히 종교적이고 부두적인 이유로 일부가 뒤 따릅니다.


1
명확히. 해제 후 포인터를 NULL로 설정하여 수정되는 double-free를 발생시키는 것을 기억하지는 않지만 그렇게하지는 못했습니다.
LnxPrgr3 2009

4
@AnT "의심스러운"은 약간입니다. 모두 유스 케이스에 따라 다릅니다. 포인터의 값이 참 / 거짓의 의미로 사용 된 경우 올바른 방법 일뿐만 아니라 최상의 방법입니다.
Coder

1
@Coder 완전히 잘못되었습니다. 포인터의 값이 진정한 거짓 의미로 사용되어 자유 호출하기 전에 객체를 가리키는 지 여부를 알면 모범 사례 일뿐 만 아니라 잘못되었습니다 . 예를 들면 다음과 같습니다 foo* bar=getFoo(); /*more_code*/ free(bar); /*more_code*/ return bar != NULL;.. 여기서, 설정 barNULL호출 후 것은하기 free가 생각하는 기능을하게됩니다 결코 바 없었다 잘못된 값을 반환!
David Schwartz

가장 큰 이점은 이중 프리를 방지하는 것이 아니라, 더 빠르고 안정적으로 매달려있는 포인터를 잡는 것입니다. 예를 들어 포함 된 메모리 포인터를 해제하고 포함 된 파일을 닫을 때 리소스, 할당 된 메모리에 대한 포인터, 파일 핸들 등을 보유하는 구조체를 해제하면 각 멤버가 NULL입니다. 그런 다음 실수로 매달려있는 포인터를 통해 리소스 중 하나에 액세스하면 프로그램은 매번 오류가 발생하는 경향이 있습니다. 그렇지 않으면 NULL이 없으면 해제 된 데이터를 아직 덮어 쓰지 않고 버그를 쉽게 재현 할 수 없습니다.
jimhark

1
잘 구조화 된 코드는 포인터가 해제 된 후 액세스되는 경우 또는 두 번 해제 된 경우를 허용해서는 안된다는 데 동의합니다. 그러나 실제 세계에서 내 코드는 아마 나를 알지 못하고 일을 제대로 수행 할 시간과 기술이없는 사람이 수정 및 / 또는 관리합니다 (마감일은 항상 어제이기 때문에). 따라서 잘못 사용하더라도 시스템을 중단시키지 않는 방탄 기능을 작성하는 경향이 있습니다.
mfloris

34

대부분의 응답은 double free를 방지하는 데 중점을 두었지만 포인터를 NULL로 설정하면 또 다른 이점이 있습니다. 포인터를 해제하면 malloc에 ​​대한 다른 호출로 해당 메모리를 재 할당 할 수 있습니다. 여전히 원래의 포인터가있는 경우 자유 변수 후 포인터를 사용하려고 시도하고 다른 변수를 손상시키려는 버그가 생길 수 있으며 프로그램이 알 수없는 상태가되고 모든 종류의 나쁜 일이 발생할 수 있습니다 ( 운이 좋지 않으면 데이터가 손상됩니다). 해제 후 포인터를 NULL로 설정 한 경우 나중에 해당 포인터를 읽거나 쓰려고하면 segfault가 발생하며 이는 일반적으로 임의의 메모리 손상보다 바람직합니다.

두 가지 이유로 free () 후에 포인터를 NULL로 설정하는 것이 좋습니다. 그러나 항상 필요한 것은 아닙니다. 예를 들어, 포인터 변수가 free () 직후 범위를 벗어나면 NULL로 설정해야 할 이유가 없습니다.


1
+1 이것은 실제로 매우 좋은 지적입니다. "더블 프리"(완전히 가짜 임)에 대한 추론 아니라, 이것 입니다. 나는 포인터의 기계적인 NULL-ing 팬 free이 아니지만 실제로 이것은 의미가 있습니다.
AnT

동일한 포인터를 통해 포인터를 해제 한 후 포인터에 액세스 할 수 있으면 다른 포인터를 통해 가리키는 객체를 해제 한 후 포인터에 액세스 할 가능성이 훨씬 높습니다. 따라서 이것은 전혀 도움이되지 않습니다. 다른 포인터를 통해 객체를 해제 한 후 한 포인터를 통해 객체에 액세스하지 못하게하려면 다른 메커니즘을 사용해야합니다. 같은 포인터 경우를 보호하기 위해 해당 방법을 사용할 수도 있습니다.
David Schwartz 10

1
@DavidSchwartz : 나는 당신의 의견에 동의하지 않습니다. 몇 주 전에 대학 운동을 위해 스택을 작성해야했을 때 문제가 발생하여 몇 시간 동안 조사했습니다. 어느 시점에서 이미 사용 가능한 메모리에 액세스했습니다 (사용 가능한 라인이 너무 빠릅니다). 그리고 때때로 그것은 매우 이상한 행동으로 이어집니다. 포인터를 해제 한 후 NULL로 포인터를 설정 한 경우 "간단한"segfault가 있었으며 몇 시간의 작업을 절약했을 것입니다. 이 답변에 +1하십시오!
mozzbozz 2016 년

2
@katze_sonne 멈춰진 시계조차 하루에 두 번입니다. NULL을 가리키는 포인터를 설정 하면 이미 해제 된 개체에 대한 잘못된 액세스가 NULL을 확인하는 코드에서 segfaulting을하지 못하도록하여 버그를 숨겨서 확인해야 할 개체를 자동으로 확인하지 못할 가능성이 훨씬 높습니다 . (아마도 특정 디버그 무료 후에 NULL 포인터를 설정하면 도움이 될, 또는 적합 할 수 있습니다 세그먼트 폴트를 보장 NULL 이외의 값을 설정 할 수 만듭니다.하지만이 어리 석음 한 번 당신을 도울 일이 있다는 것입니다 하지 의 찬성 인수 .)
David Schwartz

@DavidSchwartz 글쎄요, 합리적으로 들립니다 ... 귀하의 의견에 감사드립니다, 나는 이것을 앞으로 고려할 것입니다! :) +1
mozzbozz 2016 년

20

메모리 덮어 쓰기를 피하는 것이 좋습니다. 위의 기능에서는 불필요하지만 종종 완료되면 응용 프로그램 오류를 찾을 수 있습니다.

대신 다음과 같이 해보십시오.

#if DEBUG_VERSION
void myfree(void **ptr)
{
    free(*ptr);
    *ptr = NULL;
}
#else
#define myfree(p) do { void ** __p = (p); free(*(__p)); *(__p) = NULL; } while (0)
#endif

DEBUG_VERSION을 사용하면 디버깅 코드에서 여유 공간을 프로파일 링 할 수 있지만 기능은 동일합니다.

편집 : 아래에 제안 된대로 do ...를 추가했습니다. 감사합니다.


3
대괄호없이 if 문 다음에 사용하면 매크로 버전에 미묘한 버그가 있습니다.
Mark Ransom 2016 년

(void) 0은 무엇입니까? 이 코드는 다음과 같습니다. if (x) myfree (& x); else do_foo (); (x) {free (* (& x)); * (& x) = null; } void 0; else do_foo (); 다른 것은 오류입니다.
jmucchiello 2016 년

이 매크로는 쉼표 연산자를위한 완벽한 장소입니다 : free ( (p)), * (p) = null. 물론 다음 문제는 * (p)를 두 번 평가한다는 것입니다. {void * _pp = (p) 여야합니다 . 무료 (* _pp); * _pp = null; } 전처리 기가 재미 있지 않습니다.
jmucchiello 2016 년

5
매크로는 대괄호 안에 있으면 안되며, do { } while(0)블록 안에 있어야 if(x) myfree(x); else dostuff();깨지지 않습니다.
Chris Lutz

3
Lutz가 말했듯이, 매크로 바디 do {X} while (0)는 함수처럼 "느낌이 있고 작동하는"매크로 바디를 만드는 가장 좋은 방법은 IMO입니다. 대부분의 컴파일러는 루프를 최적화합니다.
Mike Clark

7

free () d 포인터에 도달하면 깨지거나 끊어 질 수 있습니다. 메모리가 프로그램의 다른 부분에 재 할당 된 후 메모리가 손상 될 수 있습니다.

포인터를 NULL로 설정 한 경우, 액세스하면 프로그램이 항상 segfault와 충돌합니다. 더 이상, 때로는 작동하지 않습니다 .`` 더 이상, 예측할 수없는 방식으로 충돌합니다. '' 디버깅하기가 더 쉽습니다.


5
프로그램이 항상 segfault와 충돌하는 것은 아닙니다. 포인터에 액세스하는 방식이 역 참조 전에 충분히 큰 오프셋이 적용되었다는 것을 의미하는 경우 주소 지정 가능한 메모리에 도달 할 수 있습니다. ((MyHugeStruct *) 0)-> fieldNearTheEnd. 그리고 그것은 0 액세스에서 segfault가 전혀없는 하드웨어를 다루기 전에도 마찬가지입니다. 그러나 프로그램이 segfault와 충돌 할 가능성이 높습니다.
Steve Jessop

7

포인터를 free'd 메모리'로 설정하면 포인터를 통해 해당 메모리에 액세스하려고하면 정의되지 않은 동작이 발생하는 대신 즉시 충돌이 발생합니다. 문제가 발생한 위치를 훨씬 쉽게 파악할 수 있습니다.

나는 당신의 주장을 볼 수 있습니다 : nPtr직후에 범위를 벗어나기 nPtr = NULL때문에에 설정 할 이유가없는 것 같습니다 NULL. 그러나 struct멤버 또는 포인터가 즉시 범위를 벗어나지 않는 다른 곳에서는 더 의미가 있습니다. 포인터를 사용해서는 안되는 코드에서 해당 포인터를 다시 사용할지 여부는 분명하지 않습니다.

규칙이 자동으로 규칙을 시행하는 것이 훨씬 어렵 기 때문에 개발자가 규칙을 따르지 않기 때문에이 두 경우를 구별하지 않고 규칙이 명시되어있을 가능성이 있습니다. NULL매번 무료로 포인터를 설정하는 것은 아프지 않지만 큰 문제를 지적 할 가능성이 있습니다.


7

c에서 가장 일반적인 버그는 double free입니다. 기본적으로 당신은 그런 일을

free(foobar);
/* lot of code */
free(foobar);

그리고 그것은 꽤 나빠집니다 .OS는 이미 해제 된 메모리를 해제하려고 시도하며 일반적으로 segfault입니다. 따라서 모범 사례는로 설정하는 NULL것이므로 테스트하고이 메모리를 해제해야하는지 확인할 수 있습니다.

if(foobar != null){
  free(foobar);
}

또한 free(NULL)아무것도하지 않으므로 if 문을 작성할 필요가 없습니다. 나는 실제로 OS 전문가가 아니지만 지금은 대부분의 OS가 더블 프리에서 충돌 할 것입니다.

가비지 콜렉션 (Java, dotnet)이있는 모든 언어가이 문제가없고 개발자에게 전체 메모리 관리를 맡길 필요가 없다는 점을 자랑스럽게 여기는 주된 이유이기도합니다.


11
실제로 확인하지 않고 free ()를 호출 할 수 있습니다. free (NULL)는 아무것도하지 않는 것으로 정의됩니다.
Amber

5
그게 버그를 숨기지 않습니까? (너무 많이 자유롭게하는 것처럼)
Georg Schölly

1
고마워요 p = (char *)malloc(.....); free(p); if(p!=null) //p!=null is true, p is not null although freed { free(p); //Note: checking doesnot prevent error here }
Shaobo Wang

5
내가 말했듯이, 전달 된 포인터의 값을 변경할 free(void *ptr) 수 없습니다 . 포인터 의 내용 , 해당 주소에 저장된 데이터 는 변경할 수 있지만 주소 자체 는 변경할 수 없으며 포인터값은 변경할 수 없습니다 . 그것은 free(void **ptr)(표준에 의해 허용되지 않는) 또는 매크로 (허용되고 완벽하게 이식 가능하지만 사람들은 매크로를 좋아하지 않음)를 필요로합니다. 또한 C는 편의성에 관한 것이 아니라 프로그래머가 원하는만큼 제어 할 수있게하는 것입니다. 포인터를로 설정하는 추가 오버 헤드를 원하지 NULL않으면 강요해서는 안됩니다.
Chris Lutz

2
C 코드 작성자의 일부로 전문성이 부족하다는 사실은 세상에 거의 없습니다. 그러나 "호출하기 전에 포인터의 NULL 검사 free"( "메모리 할당 함수의 결과 캐스팅"또는 ""와 함께 유형 이름 사용)와 같은 것들이 포함됩니다 sizeof.
AnT

6

이것의 뒤에 아이디어는 해제 된 포인터의 우연한 재사용을 막는 것입니다.


4

이것은 실제로 중요 할 수 있습니다. 메모리를 비워도 프로그램의 후반부에 공간에 착륙하는 새로운 무언가가 할당 될 수 있습니다. 이전 포인터는 이제 유효한 메모리 덩어리를 가리 킵니다. 그러면 누군가가 포인터를 사용하여 프로그램 상태가 유효하지 않게 될 수 있습니다.

포인터를 널 아웃하면 사용하려고하면 0x0을 역 참조하고 거기에서 충돌하므로 디버그하기 쉽습니다. 랜덤 메모리를 가리키는 랜덤 포인터는 디버그하기가 어렵습니다. 반드시 필요한 것은 아니지만 모범 사례 문서에있는 이유입니다.


Windows에서 최소한 디버그 빌드는 메모리를 0xdddddddd로 설정하므로 삭제 된 메모리에 대한 포인터를 사용하면 즉시 알 수 있습니다. 모든 플랫폼에서 유사한 메커니즘이 있어야합니다.
i_am_jorf

2
jeffamaphone, 삭제 된 메모리 블록은 포인터를 다시 사용할 때 까지 재 할당되어 다른 객체에 할당 되었을 수 있습니다.
Constantin

4

ANSI C 표준에서 :

void free(void *ptr);

free 함수는 ptr이 가리키는 공간을 할당 해제, 즉 추가 할당에 사용 가능하게합니다. ptr이 널 포인터 인 경우 조치가 발생하지 않습니다. 그렇지 않으면, 인수가 calloc, malloc 또는 realloc 함수에 의해 이전에 리턴 된 포인터와 일치하지 않거나 free 또는 realloc 호출에 의해 공간이 할당 해제 된 경우, 동작은 정의되지 않습니다.

"정의되지 않은 동작"은 거의 항상 프로그램 충돌입니다. 이를 피하기 위해 포인터를 NULL로 재설정하는 것이 안전합니다. free () 자체는 포인터에 대한 포인터가 아니라 포인터에만 전달되므로이를 수행 할 수 없습니다. 포인터를 NULL로 만드는보다 안전한 free () 버전을 작성할 수도 있습니다.

void safe_free(void** ptr)
{
  free(*ptr);
  *ptr = NULL;
}

@ DrPizza-오류 (제 생각에는 오류)는 프로그램이 예상대로 작동하지 않는 원인입니다. 숨겨진 이중 무료로 프로그램이 중단되면 오류입니다. 의도 한대로 정확하게 작동한다면 오류가 아닙니다.
Chris Lutz

@ DrPizza : NULL마스킹 오류를 피하기 위해 설정 해야하는 이유를 찾았습니다 . stackoverflow.com/questions/1025589/… 어느 경우이든 약간의 오류가 숨겨지는 것 같습니다.
Georg Schölly

1
빈 포인터 포인터 포인터에는 문제가 있습니다. c-faq.com/ptrs/genericpp.html
Secure

3
@Chris, 아닙니다. 가장 좋은 방법은 코드 구조입니다. 임의의 malloc을 던지지 말고 코드베이스 전체를 비우고 관련 사항을 함께 유지하십시오. 리소스 (메모리, 파일, ...)를 할당하는 "모듈"은 리소스를 해제하는 역할을하며 포인터를 관리하는 기능을 제공해야합니다. 특정 자원의 경우, 할당 된 장소와 해제 된 장소가 모두 서로 가깝습니다.
확보

4
@Chris Lutz : Hogwash. 동일한 포인터를 두 번 해제하는 코드를 작성하면 프로그램에 논리적 오류가 있습니다. 논리적 오류가 발생하지 않도록 마스킹한다고해서 프로그램이 올바르다는 의미는 아닙니다. 더블 프리를 쓰는 것이 정당화되는 시나리오는 없습니다.
DrPizza 2009

4

사람들이 빈 메모리 할당에 액세스 할 때 내 경험에서와 같이 이것은 거의 도움이되지 않는다는 것을 알았습니다. 어딘가에 다른 포인터가 있기 때문에 거의 항상 그렇습니다. 그런 다음 "유용한 혼란을 피하십시오"라는 다른 개인 코딩 표준과 충돌하므로 거의 도움이되지 않고 코드를 약간 읽을 수 없게 만드는 것으로 생각하지 않습니다.

그러나 포인터를 다시 사용하지 않으면 변수를 null로 설정하지 않지만 더 높은 수준의 디자인은 종종 null로 설정 해야하는 이유를 제공합니다. 예를 들어 포인터가 클래스의 멤버이고 가리키는 포인트를 삭제 한 경우 클래스를 좋아하는 경우 "계약"은 해당 멤버가 언제든지 유효한 무언가를 가리 키므로 null로 설정해야한다는 것입니다 그런 이유로. 작은 차이이지만 중요한 것 같아요.

C ++ 에서는 메모리를 할당 할 때 (스마트 포인터를 사용하지 않는 한 일부 생각이 필요한 경우) 항상 이 데이터 를 소유 하는 사람을 항상 생각하는 것이 중요합니다 . 그리고이 프로세스는 일반적으로 포인터가 일부 클래스의 멤버가되는 경향이 있으며 일반적으로 클래스가 항상 유효한 상태가되기를 원하며 가장 쉬운 방법은 멤버 변수를 NULL로 설정하여 포인트를 나타냅니다. 지금은 아무것도 아닙니다.

일반적인 패턴은 생성자에서 모든 멤버 포인터를 NULL로 설정하고 설계자가 클래스가 소유하고 있다고 말하는 데이터에 대한 포인터에서 소멸자 호출을 삭제하도록하는 것 입니다. 분명히이 경우 이전에 데이터가 없다는 것을 나타 내기 위해 무언가를 삭제할 때 포인터를 NULL로 설정해야합니다.

요약하면, 예를 들어 무언가를 삭제 한 후 포인터를 NULL로 설정하는 경우가 많지만 코딩 표준 규칙을 맹목적으로 따르는 것이 아니라 누가 데이터를 소유하는지에 대한 더 큰 디자인과 생각의 일부입니다. 나는 당신의 예제에서 그렇게하지 않을 것입니다. 그렇게 할 때 이점이 없다고 생각하고 "클러 터 (clutter)"를 추가합니다.


4

최근에 답을 찾은 후에도 같은 질문이 나왔습니다. 나는이 결론에 도달했다.

가장 좋은 방법이며 모든 (내장) 시스템에서 이식 가능하도록하려면이 지침을 따라야합니다.

free()는 라이브러리 함수이며, 플랫폼을 변경함에 따라 달라 지므로이 함수에 포인터를 전달한 후 메모리를 비운 후이 포인터가 NULL로 설정 될 것으로 예상해서는 안됩니다. 플랫폼에 대해 구현 된 일부 라이브러리의 경우에는 그렇지 않을 수 있습니다.

그래서 항상 가십시오

free(ptr);
ptr = NULL;

3

이 규칙은 다음 시나리오를 피하려고 할 때 유용합니다.

1) 복잡한 논리 및 메모리 관리 기능을 가진 정말 긴 기능을 가지고 있으며 나중에 함수에서 삭제 된 메모리에 대한 포인터를 실수로 재사용하고 싶지 않습니다.

2) 포인터는 상당히 복잡한 동작을 가진 클래스의 멤버 변수이며 실수로 다른 함수에서 삭제 된 메모리에 포인터를 재사용하고 싶지 않습니다.

귀하의 시나리오에서는 의미가 없지만, 기능이 더 길어지면 문제가 될 수 있습니다.

NULL로 설정하면 나중에 논리 오류를 실제로 마스킹 할 수 있으며, 유효하다고 가정하는 경우 여전히 NULL에서 충돌하므로 중요하지 않습니다.

일반적으로 좋은 생각이라고 생각할 때 NULL로 설정하고 가치가 없다고 생각하면 귀찮게하지 않는 것이 좋습니다. 대신 짧은 기능과 잘 설계된 수업을 작성하는 데 집중하십시오.


2

다른 사람들이 말한 것을 덧붙이려면 포인터 사용법의 좋은 방법 중 하나는 항상 유효한 포인터인지 여부를 확인하는 것입니다. 다음과 같은 것 :


if(ptr)
   ptr->CallSomeMethod();

해제 후 포인터를 명시 적으로 NULL로 표시하면 C / C ++에서 이러한 종류의 사용이 가능합니다.


5
대부분의 경우 NULL 포인터가 의미가없는 경우 어설 션을 작성하는 것이 좋습니다.
Erich Kitzmueller

2

이것은 NULL에 대한 모든 포인터를 초기화하는 인수가 될 수 있지만 다음과 같은 것은 매우 부적절한 버그 일 수 있습니다.

void other_func() {
  int *p; // forgot to initialize
  // some unrelated mallocs and stuff
  // ...
  if (p) {
    *p = 1; // hm...
  }
}

void caller() {
  some_func();
  other_func();
}

p스택과 같은 위치에 전자 nPtr와 같이 끝나 므로 여전히 유효한 포인터가 포함될 수 있습니다. 에 할당하면 *p관련이없는 모든 종류의 것들을 덮어 추한 버그로 이어질 수 있습니다. 특히 컴파일러가 디버그 모드에서 로컬 변수를 0으로 초기화하지만 최적화가 설정되지 않은 경우. 릴리스 빌드가 무작위로 폭발하는 동안 디버그 빌드에는 버그의 징후가 표시되지 않습니다 ...


2

방금 해제 된 포인터를 NULL로 설정하는 것은 필수는 아니지만 좋은 방법입니다. 이 방법으로, 당신은 1) 해방 뾰족한 사용 2) 무료로 피하는 것을 피할 수 있습니다


2

NULL에 대한 포인터 설정은 소위 double-free를 다시 보호하는 것입니다. 해당 주소에서 블록을 재할 당하지 않고 같은 주소에 대해 free ()가 두 번 이상 호출되는 상황입니다.

Double-free는 정의되지 않은 동작 (일반적으로 힙 손상 또는 즉시 프로그램 충돌)으로 이어집니다. NULL 포인터에 대해 free ()를 호출하면 아무 작업도 수행되지 않으므로 안전합니다.

따라서 free () 직후에 포인터가 스코프를 즉시 또는 아주 빨리 떠나는 것이 아닌 한 가장 좋은 방법은 포인터를 NULL로 설정하는 것이므로 free ()가 다시 호출 되더라도 NULL 포인터와 정의되지 않은 동작에 대해 호출됩니다. 회피된다.


2

아이디어는 더 이상 유효하지 않은 포인터를 해제 한 후 역 참조하려고하면 조용하고 신비하지 않고 열심히 실패하기를 원한다는 것입니다.

하지만 ... 조심하세요. NULL을 역 참조하면 모든 시스템에서 segfault가 발생하는 것은 아닙니다. AIX에서 (적어도 일부 버전) * (int *) 0 == 0이며 Solaris는이 AIX "기능"과 선택적으로 호환됩니다.


2

원래 질문 : 내용을 비운 후 포인터를 NULL로 직접 설정하는 것은 코드가 모든 요구 사항을 충족하고 완전히 디버깅되어 다시 수정되지 않으면 시간 낭비입니다. 반면에, 해제 된 포인터를 방어 적으로 NULL로 지정하면 누군가 원래 모듈의 디자인이 정확하지 않은 경우 free () 아래에 새로운 코드 블록을 무심코 추가 할 때 매우 유용 할 수 있습니다. -컴파일-하지만하지 말아야 할 것-원하는-버그.

어느 시스템에서나 옳은 일을 가장 쉽게하는 목표와 달성 할 수없는 부정확 한 측정 비용이 있습니다. C에서는 숙련 된 작업자가 많은 것을 만들 수 있으며 매우 부적절하게 취급 할 경우 모든 종류의 은유 적 상해를 입힐 수있는 매우 날카 롭고 매우 강력한 도구를 제공합니다. 일부는 올바르게 이해하거나 사용하기가 어렵습니다. 그리고 자연스럽게 위험을 회피하는 사람들은 NULL 값에 대한 포인터를 확인하기 전에 비합리적인 일을합니다.

측정 문제는 좋지 않은 것과 좋은 것을 나누려고 할 때마다 더 복잡한 경우, 모호한 측정을 얻을 가능성이 높다는 것입니다. 목표가 좋은 관행만을 유지하는 것이라면, 모호한 것들이 실제로는 좋지 않은 것으로 던져 질 수 있습니다. 당신의 목표가 좋지 않은 것을 제거하는 것이라면, 모호함이 좋은 것과 함께있을 수 있습니다. 좋은 목표 만 유지하거나 분명하게 나쁜 것을 제거하는 두 가지 목표는 정반대 적으로 반대되는 것처럼 보이지만 일반적으로 둘 중 하나도 아니고 다른 것도 아닌 세 번째 그룹이 있습니다.

품질 부서에 문의하기 전에 잘못된 포인터 값으로 인해 문제를 기록해야하는 빈도를 확인하려면 버그 데이터베이스를 살펴보십시오. 실제 차이를 만들고 싶다면 프로덕션 코드에서 가장 일반적인 문제를 식별하고이를 방지하는 세 가지 방법을 제안하십시오.


좋은 대답입니다. 한 가지만 추가하고 싶습니다. 버그 데이터베이스를 검토하는 것은 여러 가지 이유로하는 것이 좋습니다. 그러나 원래 질문의 맥락에서, 얼마나 많은 유효하지 않은 포인터 문제가 예방되었는지, 또는 최소한 버그 데이터베이스에 그것을 만들지 않도록 조기에 잡히는 것이 어렵다는 것을 명심하십시오. 버그 기록은 코딩 규칙을 추가하기위한 더 나은 증거를 제공합니다.
jimhark

2

두 가지 이유가 있습니다.

이중 해제시 충돌 방지

RageZ 에 의해 중복 질문으로 작성되었습니다 .

c에서 가장 일반적인 버그는 double free입니다. 기본적으로 당신은 그런 일을

free(foobar);
/* lot of code */
free(foobar);

그리고 그것은 꽤 나빠집니다 .OS는 이미 해제 된 메모리를 해제하려고 시도하며 일반적으로 segfault입니다. 따라서 모범 사례는로 설정하는 NULL것이므로 테스트하고이 메모리를 해제해야하는지 확인할 수 있습니다.

if(foobar != NULL){
  free(foobar);
}

또한 free(NULL) 아무것도하지 않으므로 if 문을 작성할 필요가 없습니다. 나는 실제로 OS 전문가가 아니지만 지금은 대부분의 OS가 더블 프리에서 충돌 할 것입니다.

이것이 가비지 콜렉션 (Java, dotnet)을 가진 모든 언어가이 문제가없고 메모리 관리 전체를 개발자에게 맡길 필요가 없다는 것을 자랑스럽게 여기는 주된 이유이기도합니다.

이미 해제 된 포인터를 사용하지 마십시오

마틴 V. Löwis A의 다른 답변 .

사용하지 않는 포인터를 NULL로 설정하는 것은 방어적인 스타일로, 매달려있는 포인터 버그를 방지합니다. 댕글 링 포인터가 해제 된 후 액세스되면 임의 메모리를 읽거나 덮어 쓸 수 있습니다. 널 포인터에 액세스하면 대부분의 시스템에서 즉시 충돌이 발생하여 오류가 무엇인지 즉시 알려줍니다.

지역 변수의 경우, 포인터가 해제 된 후에 더 이상 포인터에 액세스하지 않는 것이 "분명한"경우에는 의미가 없습니다. 따라서이 스타일은 멤버 데이터 및 전역 변수에 더 적합합니다. 로컬 변수의 경우에도 메모리가 해제 된 후에 함수가 계속되면 좋은 접근 방법 일 수 있습니다.

스타일을 완성하려면 포인터에 실제 포인터 값이 할당되기 전에 포인터를 NULL로 초기화해야합니다.


1

품질 보증팀을 운영하고 있으므로 품질 관리에 대한 사소한 부분을 추가하겠습니다. C에 대한 일부 자동 QA 도구는 해제 된 포인터에 대한 할당을 ptr" 무용 한 할당"으로 플래그합니다 . 예를 들어 Gimpel Software의 PC-lint / FlexeLint는 tst.c 8 Warning 438: Last value assigned to variable 'nPtr' (defined at line 5) not used

메시지를 선택적으로 억제하는 방법이 있으므로 팀에서 결정한 경우에도 두 가지 품질 보증 요구 사항을 모두 충족 할 수 있습니다.


1

다음 과 같이 NULL을 사용 하여 포인터 변수를 선언하는 것이 좋습니다 .

int *ptr = NULL;

ptr0x1000 메모리 주소를 가리키고 있다고 가정 해 봅시다 . 을 사용한 후에는 free(ptr)다시 NULL 로 선언하여 포인터 변수를 무효화하는 것이 좋습니다 . 예 :

free(ptr);
ptr = NULL;

NULL로 다시 선언되지 않은 경우 에도 포인터 변수가 계속 동일한 주소 ( 0x1000 ) 를 가리키면 이 포인터 변수를 댕글 링 포인터 라고합니다 . 다른 포인터 변수 ( q ) 를 정의 하고 주소를 새 포인터에 동적으로 할당하면 새 포인터 변수로 동일한 주소 ( 0x1000 )를 사용할 수 있습니다. 경우에, 같은 포인터 (사용하는 경우 PTR을 )과 주소가 같은 포인터 (가리키는에서 값을 업데이트 PTR ), 다음 프로그램은 어디 장소에 값을 쓰는 끝날 q는 이후 (가리키는 PQ 있습니다 동일한 주소를 가리키는 (가 0x1000 )).

예 :

*ptr = 20; //Points to 0x1000
free(ptr);
int *q = (int *)malloc(sizeof(int) * 2); //Points to 0x1000
*ptr = 30; //Since ptr and q are pointing to the same address, so the value of the address to which q is pointing would also change.

1

간단히 말해 : 실수로 실수로 해제 한 주소에 액세스하고 싶지 않습니다. 주소를 비울 때 힙의 해당 주소를 다른 응용 프로그램에 할당 할 수 있습니다.

그러나 포인터를 NULL로 설정하지 않은 경우 실수로 포인터를 역 참조하거나 해당 주소의 값을 변경하십시오. 당신은 아직도 그것을 할 수 있습니다. 그러나 당신이 논리적으로하고 싶은 말은 아닙니다.

사용 가능한 메모리 위치에 여전히 액세스 할 수있는 이유는 무엇입니까? 이유 : 메모리를 비 웠지만 포인터 변수에 여전히 힙 메모리 주소에 대한 정보가 있습니다. 따라서 방어 전략으로 NULL로 설정하십시오.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.