Visual Studio는 삭제 된 포인터로 무엇을하며 그 이유는 무엇입니까?


130

필자가 읽은 C ++ 책은 포인터를 delete연산자를 사용하여 삭제하면 가리키는 위치의 메모리가 "해제"되어 덮어 쓸 수 있다고 말합니다. 또한 포인터가 재 할당되거나로 설정 될 때까지 동일한 위치를 계속 가리킬 것임을 나타냅니다.NULL .

그러나 Visual Studio 2012에서는; 이것은 사실이 아닌 것 같습니다!

예:

#include <iostream>

using namespace std;

int main()
{
    int* ptr = new int;
    cout << "ptr = " << ptr << endl;
    delete ptr;
    cout << "ptr = " << ptr << endl;

    system("pause");

    return 0;
}

이 프로그램을 컴파일하고 실행하면 다음과 같은 결과가 나타납니다.

ptr = 0050BC10
ptr = 00008123
Press any key to continue....

delete가 호출 될 때 포인터가 가리키는 주소는 분명히 변경됩니다!

왜 이런 일이 발생합니까? 이것은 Visual Studio와 관련이 있습니까?

그리고 delete가 어쨌든 가리키는 주소를 변경할 수 있다면 왜 NULL임의의 주소 대신 포인터를 자동으로 설정하지 않습니까?


4
포인터를 삭제해도 NULL로 설정된다는 의미는 아닙니다. 처리해야합니다.
Matt

11
나는 알고 있지만, 내가 읽고있는 책에는 삭제하기 전에 가리키는 것과 동일한 주소가 여전히 포함되어 있지만 해당 주소의 내용을 덮어 쓸 수 있다고 말합니다.
tjwrona1992

6
@ tjwrona1992, 그렇습니다. 이것이 일반적으로 일어나는 일이기 때문입니다. 이 책은 어려운 규칙이 아니라 가장 가능성있는 결과 만 나열합니다.
SergeyA

5
@ tjwrona1992 내가 읽고있는 C ++ 책 -그 책의 이름은 ...?
PaulMcKenzie

4
@ tjwrona1992 : 놀랍지 만, 역 참조뿐만 아니라 정의되지 않은 동작 인 잘못된 포인터 값을 모두 사용하는 것입니다. "포인팅 위치 확인"은 허용되지 않는 방식으로 값을 사용합니다.
Ben Voigt

답변:


175

저장된 주소 ptr가 항상 덮어 쓰여지는 것을 알았 습니다.00008123 ...

이것은 이상하게 보였으므로 약간의 파고를했고 "C ++ 객체를 삭제할 때 자동 포인터 삭제"에 대해 설명하는 섹션이 포함 된 이 Microsoft 블로그 게시물 을 찾았 습니다 .

... NULL 검사는 공통 코드 구성을 의미합니다. 기존의 NULL 검사는 살균 값으로 NULL을 사용하는 것과 결합하여 근본 원인을 해결해야하는 실제 메모리 안전 문제를 숨길 수 있습니다.

이러한 이유로 우리는 살균 값으로 0x8123을 선택했습니다. 운영 체제 관점에서 이것은 제로 주소 (NULL)와 동일한 메모리 페이지에 있지만 0x8123에서의 액세스 위반은보다 세심한주의가 필요하므로 개발자에게 더 잘 드러날 것입니다. .

Visual Studio가 포인터를 삭제 한 후 포인터로 수행하는 작업을 설명 할뿐만 아니라 NULL자동으로 설정하지 않기로 선택한 이유에 대한 답변도 제공합니다 .


이 "기능"은 "SDL 검사"설정의 일부로 활성화됩니다. 활성화 / 비활성화하려면 PROJECT-> 속성-> 구성 속성-> C / C ++-> 일반-> SDL 검사로 이동하십시오.

이를 확인하려면 다음을 수행하십시오.

이 설정을 변경하고 동일한 코드를 다시 실행하면 다음과 같은 출력이 생성됩니다.

ptr = 007CBC10
ptr = 007CBC10

"기능"은 살균 삭제를 호출, 같은 위치에 두 개의 포인터를 가지고 있기 때문에 경우에 따옴표에 하나 그들 중입니다. 다른 하나는 잘못된 위치를 가리키고 있습니다 ...


최신 정보:

5 년 이상의 C ++ 프로그래밍 경험을 통해이 모든 문제가 기본적으로 문제가된다는 것을 알게되었습니다. C ++ 프로그래머이고 스마트 포인터를 사용하지 않고 원시 포인터를 사용 new하고 delete관리하는 경우 (이 전체 문제를 우회) C 프로그래머가되기 위해 경력 경로를 변경하는 것이 좋습니다. ;)


12
좋은 발견입니다. MS가 이와 같은 디버깅 동작을 더 잘 문서화하기를 바랍니다. 예를 들어, 어떤 컴파일러 버전에서 구현을 시작했는지, 어떤 옵션이 동작을 활성화 / 비활성화하는지 알고 있으면 좋을 것입니다.
Michael Burr

5
"운영 체제 관점에서 이것은 제로 주소와 동일한 메모리 페이지에 있습니다"-허? x86의 표준 (큰 페이지 무시) 페이지 크기가 여전히 Windows와 Linux 모두에서 4kb가 아닙니까? Raymond Chen의 블로그에있는 주소 공간의 첫 64kb에 대해서는 약간 기억이 나지 않지만 실제로는 같은 결과를
얻습니다

12
@Voo windows는 트래핑을위한 데드 스페이스로 64kB 상당의 RAM을 예약합니다. 0x8123 훌륭하게 거기에 빠지다
래칫 괴물

7
실제로, 나쁜 습관을 조장하지 않으며 포인터를 NULL로 설정하는 것을 건너 뛸 수 없습니다. 이것이 0x8123대신 대신 사용하는 전체 이유 입니다 0. 포인터는 여전히 유효하지 않지만 역 참조를 시도 할 때 예외가 발생하고 (양호) NULL 검사를 통과 하지 않습니다 ( 이는 수행하지 않는 오류이므로 양호). 나쁜 습관이있는 곳은 어디입니까? 실제로 디버깅하는 데 도움이되는 것입니다.
Luaan

3
글쎄, 둘 다 설정할 수는 없으므로 두 번째로 좋은 옵션입니다. 마음에 들지 않으면 SDL 검사를 끄십시오. 특히 다른 사람의 코드를 디버깅 할 때 유용합니다.
Luaan

30

/sdl컴파일 옵션의 부작용이 나타납니다 . VS2015 프로젝트에 대해 기본적으로 켜져 있으며 / gs에서 제공하는 것 이외의 추가 보안 검사가 가능합니다. 프로젝트> 속성> C / C ++> 일반> SDL 검사 설정을 사용하여 변경하십시오.

MSDN 기사 에서 인용 :

  • 제한된 포인터 살균을 수행합니다. 역 참조를 포함하지 않는 표현식과 사용자 정의 소멸자가없는 유형에서는 포인터 호출이 삭제 호출 후 유효하지 않은 주소로 설정됩니다. 이로 인해 오래된 포인터 참조가 재사용되지 않습니다.

삭제 된 포인터를 NULL로 설정하는 것은 MSVC를 사용할 때 좋지 않은 방법입니다. 디버그 힙과이 / sdl 옵션 모두에서 얻는 도움말을 무시하므로 더 이상 프로그램에서 유효하지 않은 비어있는 / 삭제 호출을 감지 할 수 없습니다.


1
확인했습니다. 이 기능을 비활성화하면 포인터가 더 이상 리디렉션되지 않습니다. 수정하는 실제 설정을 제공해 주셔서 감사합니다!
tjwrona1992

한스, 같은 위치를 가리키는 두 개의 포인터가있는 경우 삭제 된 포인터를 NULL로 설정하는 것은 여전히 ​​나쁜 습관으로 간주됩니까? 당신이 때 delete하나, 비주얼 스튜디오는 원래 위치로 두 번째 포인터를 가리키는 떠나 지금은 무효이다.
tjwrona1992

1
포인터를 NULL로 설정하면 어떤 종류의 마술이 일어날 지 분명하지 않습니다. 다른 포인터는 아무것도 해결하지 않으므로 버그를 찾으려면 디버그 할당자가 여전히 필요합니다.
Hans Passant

3
VS는 포인터를 정리 하지 않습니다 . 그것들이 손상됩니다. 따라서 어쨌든 사용할 때 프로그램이 중단됩니다. 디버그 할당자는 힙 메모리와 거의 동일한 작업을 수행합니다. NULL의 큰 문제는 충분히 손상되지 않은 것입니다. 그렇지 않으면 일반적인 전략은 Google "0xdeadbeef"입니다.
Hans Passant

1
포인터를 NULL로 설정하는 것이 여전히 유효하지 않은 이전 주소를 가리키는 것보다 훨씬 낫습니다. NULL 포인터에 쓰려고 시도해도 데이터가 손상되지 않으며 프로그램이 중단 될 수 있습니다. 그 시점에서 포인터를 재사용하려고 시도해도 프로그램이 충돌하지 않을 수 있으며 예측할 수없는 결과를 초래할 수 있습니다!
tjwrona1992

19

또한 포인터가 재 할당되거나 NULL로 설정 될 때까지 포인터가 계속 동일한 위치를 가리킬 것입니다.

그것은 확실히 잘못된 정보입니다.

delete가 호출 될 때 포인터가 가리키는 주소는 분명히 변경됩니다!

왜 이런 일이 발생합니까? 이것은 Visual Studio와 관련이 있습니까?

이것은 분명히 언어 사양 내에 있습니다. ptr에 호출 한 후 유효하지 않습니다 delete. d ptr이후에 사용하면 delete정의되지 않은 동작이 발생합니다. 하지마 런타임 환경은에 ptr대한 호출 후 원하는 것을 자유롭게 수행 할 수 delete있습니다.

그리고 delete가 어쨌든 가리키는 주소를 변경할 수 있다면 왜 임의의 주소 대신 포인터를 자동으로 NULL로 설정하지 않습니까?

포인터 값을 이전 값으로 변경하는 것은 언어 사양 내에 있습니다. NULL로 변경하는 한, 그것은 나쁠 것입니다. 포인터의 값이 NULL로 설정되면 프로그램은보다 정상적인 방식으로 작동합니다. 그러나 문제가 숨겨집니다. 프로그램이 다른 최적화 설정으로 컴파일되거나 다른 환경으로 이식되면 문제가 가장 부적절한 순간에 나타날 수 있습니다.


1
나는 그것이 OP의 질문에 대답한다고 믿지 않습니다.
SergeyA

편집 한 후에도 동의하지 않습니다. NULL로 설정하면 문제가 숨겨지지 않습니다. 실제로, 그것 없이는 더 많은 경우에 노출됩니다. 일반적인 구현이 이것을하지 않는 이유가 있으며 그 이유는 다릅니다.
SergeyA

4
@ SergeyA, 대부분의 구현은 효율성을 위해 그것을하지 않습니다. 그러나 구현에서 설정하기로 결정한 경우 NULL이 아닌 것으로 설정하는 것이 좋습니다. NULL로 설정 한 경우보다 빨리 문제를 드러냅니다. delete포인터로 두 번 호출 해도 문제가 발생하지 않습니다. NULL로 설정됩니다 . 확실히 좋지 않습니다.
R Sahu

아니요, 효율성이 아닙니다. 적어도 주요 관심사는 아닙니다.
SergeyA

7
@SergeyA NULL프로세스 주소 공간이 아닌 값에 대한 포인터를 설정 하면 두 가지 대안보다 더 많은 사례가 노출됩니다. 매달려있는 상태로 두었다가 해방 된 후에 사용되는 경우 반드시 segfault를 유발하지는 않습니다. 다시 설정하면 NULLsegfault가 발생하지 않습니다 delete.
Blacklight Shining

10
delete ptr;
cout << "ptr = " << ptr << endl;

일반도에서 읽기 (위에서처럼, 참고 :이 역 참조 다릅니다) 유효하지 않은 포인터의 값 (당신이 때 포인터가 예를 들어 무효가 delete구현 정의 된 동작입니다 그것은). 이것은 CWG # 1438 에서 소개되었습니다 . 여기도 참조 하십시오 .

유효하지 않은 포인터의 값을 읽는 것은 정의되지 않은 행동이기 때문에, 위의 내용은 정의되지 않은 행동이므로 모든 일이 발생할 수 있습니다.


3
또한 관련은에서 인용이다 [basic.stc.dynamic.deallocation]"표준 라이브러리에서 해제 함수에 주어진 인수가 널 포인터 값이 아닌 포인터 인 경우, 할당 취소 함수가 참조하는 모든 포인터를 무효 렌더링, 포인터가 참조하는 스토리지 할당을 취소하여야은 : 할당되지 않은 스토리지의 일부 "및 [conv.lval](섹션 4.1)의 유효하지 않은 포인터 값 읽기 (lvalue-> rvalue 변환) 규칙 은 구현 정의 동작입니다.
Ben Voigt

UB조차도 특정 벤더에 의해 특정 방식으로 구현되어 적어도 해당 컴파일러에 대해 신뢰할 수 있습니다. Microsoft가 CWG # 1438 이전에 포인터 위생 기능을 구현하기로 결정했다면, 그 기능을 어느 정도 신뢰할 수있게 만들지 않았을 것입니다. 특히 해당 기능이 켜져 있으면 "어떤 일이 일어날 수있다"는 것은 사실이 아닙니다. 표준의 내용에 관계없이
Kyle Strand

@KyleStrand : 기본적으로 UB ( blog.regehr.org/archives/213 )에 대한 정의를 제공했습니다 .
giorgim

1
SO의 대부분의 C ++ 커뮤니티에서는 "어떤 일이든 일어날 수있다"는 말 그대로 너무나 취 합니다. 나는 이것이 말도 안된다고 생각합니다 . 나는 UB의 정의를 이해하지만 컴파일러는 실제 사람들이 구현 한 소프트웨어 일 뿐이며, 사람들이 컴파일러가 특정 방식으로 작동하도록 컴파일러를 구현하는 경우 표준의 내용에 관계없이 컴파일러가 어떻게 작동 하는지 이해합니다. .
Kyle Strand

1

나는 당신이 일종의 디버그 모드를 실행 중이고 VS가 포인터를 알려진 위치로 다시 지정하려고 시도하여 역 참조를 시도하면 추적 및보고 될 수 있다고 생각합니다. 릴리스 모드에서 동일한 프로그램을 컴파일 / 실행하십시오.

delete효율성을 위해 그리고 안전에 대한 잘못된 생각을 피하기 위해 일반적으로 포인터는 내부 에서 변경되지 않습니다 . 삭제 포인터가이 위치를 가리키는 몇 개 중 하나 일 가능성이 있기 때문에 대부분의 복잡한 시나리오에서 삭제 포인터를 사전 정의 된 값으로 설정하면 좋지 않습니다.

사실, 내가 그것에 대해 더 많이 생각할수록 평소와 같이 그렇게 할 때 VS가 잘못되었다는 것을 알게됩니다. 포인터가 const라면 어떨까요? 여전히 변경 될까요?


그래도 상수 포인터 조차도이 신비로운 8123으로 리디렉션됩니다!
tjwrona1992

VS에 또 다른 돌이 있습니다.) 오늘 아침 누군가 VS 대신 g ++를 사용해야하는 이유를 물었습니다. 여기 간다.
SergeyA

7
@SergeyA하지만 포인터가 삭제 된 포인터를 DEREF하려한다는 세그먼트 폴트에 의해을 보여줍니다 삭제 다른 측면 dereffing에서 이 NULL과 동일하지 않습니다. 다른 경우에는 페이지도 비워지면 충돌이 발생합니다 (매우 가능성이 낮음). 더 빨리 실패하십시오. 빨리 해결하십시오.
ratchet freak

1
@ratchetfreak "빠른 실패, 빨리 해결"은 매우 귀중한 진언이지만 "주요 법 의학적 증거를 파괴하여 빨리 실패"는 그런 귀중한 진언을 시작하지 않습니다. 간단한 경우에는 편리하지만 더 복잡한 경우 (가장 도움이 필요한 경우), 중요한 정보를 지우면 문제를 해결하는 데 사용할 수있는 도구가 줄어 듭니다.
Cort Ammon

2
@ tjwrona1992 : Microsoft는 제 생각에 옳은 일을하고 있습니다. 하나의 포인터를 살균하는 것이 전혀 수행하지 않는 것보다 낫습니다. 디버깅으로 인해 문제가 발생하면 잘못된 삭제 호출 전에 중단 점을 두십시오. 이상한 점은 이와 같은 것이 없으면 결코 문제를 발견하지 못할 것입니다. 이러한 버그를 찾을 수있는 더 나은 솔루션이 있다면이 버그를 사용하십시오. 왜 Microsoft가하는 일에 관심이 있습니까?
Zan Lynx

0

포인터를 삭제 한 후에 가리키는 메모리는 여전히 유효 할 수 있습니다. 이 오류를 나타 내기 위해 포인터 값은 명백한 값으로 설정됩니다. 이것은 실제로 디버깅 프로세스에 도움이됩니다. 값이로 설정된 NULL경우 프로그램 흐름에서 잠재적 인 버그로 표시되지 않을 수 있습니다. 따라서 나중에 테스트 할 때 버그를 숨길 수 있습니다 NULL.

또 다른 요점은 일부 런타임 최적화 프로그램이 해당 값을 확인하고 결과를 변경할 수 있다는 것입니다.

이전에는 MS가 값을로 설정했습니다 0xcfffffff.

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