void 포인터를 삭제하는 것이 안전합니까?


96

다음 코드가 있다고 가정합니다.

void* my_alloc (size_t size)
{
   return new char [size];
}

void my_free (void* ptr)
{
   delete [] ptr;
}

안전한가요? 아니면 삭제 ptr하기 char*전에 캐스팅 해야 합니까?


왜 스스로 메모리 관리를하고 있습니까? 어떤 데이터 구조를 만들고 있습니까? 명시적인 메모리 관리를 수행해야하는 것은 C ++에서 매우 드뭅니다. 일반적으로 STL (또는 부스트에서)을 처리하는 클래스를 사용해야합니다.
Brian

6
읽는 사람들을 위해 win C ++에서 스레드에 대한 매개 변수로 void * 변수를 사용합니다 (_beginthreadex 참조). 보통 그들은 수업을 단호하게 가리 킵니다.
ryansstack 2009-06-02

3
이 경우 할당 추적 통계 또는 최적화 된 메모리 풀을 포함 할 수있는 신규 / 삭제를위한 범용 래퍼입니다. 다른 경우에는 개체 포인터가 void * 멤버 변수로 잘못 저장되고 적절한 개체 유형으로 다시 캐스팅하지 않고 소멸자에서 잘못 삭제 된 것을 보았습니다. 그래서 저는 안전 / 함정에 대해 궁금했습니다.
An̲̳̳drew 21:32:09

1
new / delete에 대한 범용 래퍼의 경우 new / delete 연산자를 오버로드 할 수 있습니다. 사용하는 환경에 따라 할당을 추적하기 위해 메모리 관리에 연결될 수 있습니다. 무엇을 삭제할지 모르는 상황에 처하게된다면 디자인이 차선책이며 리팩토링이 필요하다는 강력한 힌트로 받아들이십시오.
Hans

38
질문에 대답하는 대신 질문하는 것이 너무 많다고 생각합니다. (여기뿐만 아니라 모든 상황에서)
Petruza 2011

답변:


24

그것은 "안전"에 달려 있습니다. 할당 자체에 대한 포인터와 함께 정보가 저장되기 때문에 일반적으로 작동하므로 할당 해제자가 올바른 위치로 반환 할 수 있습니다. 이러한 의미에서 할당자가 내부 경계 태그를 사용하는 한 "안전"합니다. (많은 사람들 이요)

그러나 다른 답변에서 언급했듯이 void 포인터를 삭제하면 소멸자가 호출되지 않아 문제가 될 수 있습니다. 그런 의미에서 "안전"하지 않습니다.

당신이하고있는 일을 당신이하고있는 방식대로 할 이유가 없습니다. 자체 할당 해제 함수를 작성하려면 함수 템플릿을 사용하여 올바른 유형의 함수를 생성 할 수 있습니다. 이를 수행하는 좋은 이유는 특정 유형에 대해 매우 효율적일 수있는 풀 할당자를 생성하는 것입니다.

다른 답변에서 언급했듯이 이것은 C ++에서 정의되지 않은 동작 입니다. 일반적으로 주제 자체가 복잡하고 상반되는 의견으로 가득 차 있지만 정의되지 않은 동작을 피하는 것이 좋습니다.


9
이것은 어떻게 받아 들여진 대답입니까? "보이드 포인터를 삭제"하는 것은 말이되지 않습니다. 안전은 논점입니다.
Kerrek SB 2013 년

6
"당신이하고있는 일을 당신이하는 방식대로 할 이유가 없습니다." 그것은 사실이 아니라 당신의 의견입니다.
rxantos

8
@rxantos C ++에서 질문 작성자가 원하는 작업을 수행하는 것이 좋은 아이디어 인 카운터 예제를 제공합니다.
Christopher

나는이 대답이 실제로 대부분 합리적이라고 생각하지만,이 질문에 대한 대답 은 적어도 이것이 정의되지 않은 행동이라는 것을 언급 해야 한다고 생각합니다 .
Kyle Strand

@Christopher 유형에 따라 다르지만 단순히 작동하는 단일 가비지 수집기 시스템을 작성해보십시오. sizeof(T*) == sizeof(U*)모두 를 위한 사실 T,U은 하나의 비 템플릿 void *기반 가비지 수집기 구현 을 가질 수 있어야 함을 시사합니다 . 그러나 gc가 실제로 포인터를 삭제 / 해제해야 할 때 정확히이 질문이 발생합니다. 작동하게하려면 람다 함수 소멸자 래퍼 (urgh)가 필요하거나 유형과 저장 가능한 항목 사이를 오갈 수있는 일종의 동적 "데이터로서의 유형"유형이 필요합니다.
BitTickler 19

147

void 포인터를 통한 삭제는 C ++ 표준에 정의되어 있지 않습니다-섹션 5.3.5 / 3 참조 :

첫 번째 대안 (객체 삭제)에서 피연산자의 정적 유형이 동적 유형과 다른 경우 정적 유형은 피연산자의 동적 유형의 기본 클래스이고 정적 유형에는 가상 소멸자가 있거나 동작이 정의되지 않습니다. . 두 번째 대안 (배열 삭제)에서는 삭제할 객체의 동적 유형이 정적 유형과 다른 경우 동작이 정의되지 않습니다.

그리고 각주 :

이것은 void * 유형의 개체가 없기 때문에 void * 유형의 포인터를 사용하여 개체를 삭제할 수 없음을 의미합니다.

.


6
정확한 견적을 받으 셨나요? 각주는 다음 텍스트를 참조하고 있다고 생각합니다. "첫 번째 대안 (객체 삭제)에서 피연산자의 정적 유형이 동적 유형과 다른 경우 정적 유형은 피연산자의 동적 유형과 정적 유형의 기본 클래스가됩니다. type은 가상 소멸자를 갖거나 동작이 정의되지 않습니다. 두 번째 대안 (배열 삭제)에서는 삭제할 객체의 동적 유형이 정적 유형과 다른 경우 동작이 정의되지 않습니다. " :)
Johannes Schaub-litb

2
당신이 옳습니다-나는 대답을 업데이트했습니다. 나는 그것이 기본 포인트를 부정한다고 생각하지 않습니까?

물론 아닙니다. 여전히 UB라고되어 있습니다. 더욱이 이제는 void * 삭제가 UB라고 규범 적으로 명시하고 있습니다. :)
Johannes Schaub-litb

NULL응용 프로그램 메모리 관리를 위해 차이를 만들어 보이드 포인터의 지적 주소 메모리를 채우 시겠습니까?
artu-hnrq

25

그것은 좋은 생각이 아니며 C ++에서 할 일이 아닙니다. 이유없이 유형 정보를 잃어 버리고 있습니다.

소멸자는 원시 유형이 아닌 유형에 대해 호출 할 때 삭제중인 배열의 객체에 대해 호출되지 않습니다.

대신 new / delete를 재정의해야합니다.

void *를 삭제하면 우연히 올바르게 메모리가 해제되지만 결과가 정의되지 않았기 때문에 잘못된 것입니다.

나에게 알려지지 않은 이유로 포인터를 void *에 저장하고 해제해야한다면 malloc과 free를 사용해야합니다.


5
소멸자가 호출되지 않았다는 것은 맞지만 크기를 알 수 없다는 것은 틀 렸습니다. 당신은 당신이 새로운에서 가지고있는 포인터를 삭제주는 경우에, 그것은 않는 사실에 완전히 떨어져 유형에서, 물건의 크기가 삭제되는 것을 알고있다. 그것이 어떻게 수행되는지는 C ++ 표준에 의해 지정되지 않았지만 'new'가 반환하는 포인터가 가리키는 데이터 바로 전에 크기가 저장되는 구현을 보았습니다.
KeyserSoze

C ++ 표준에 정의되어 있지 않지만 크기에 대한 부분을 제거했습니다. 나는 malloc / free가 void * 포인터에 대해 작동한다는 것을 알고 있습니다.
Brian R. Bondy

표준의 관련 섹션에 대한 웹 링크가 있다고 가정하지 않습니까? 필자가 살펴본 new / delete의 몇 가지 구현이 유형 지식 없이는 확실히 올바르게 작동한다는 것을 알고 있지만 표준이 지정하는 내용을 보지 못했다는 것을 인정합니다. IIRC C ++는 원래 배열을 삭제할 때 배열 요소 개수가 필요했지만 더 이상 최신 버전에서는 필요하지 않습니다.
KeyserSoze 2009-06-02

@Neil Butterworth 답변을 참조하십시오. 그의 대답은 제 생각에 받아 들여 져야합니다.
Brian R. Bondy

2
@keysersoze : 일반적으로 귀하의 진술에 동의하지 않습니다. 일부 구현에서 할당 된 메모리 전에 크기를 저장했다고해서 이것이 규칙이라는 의미는 아닙니다.

13

void 포인터를 삭제하는 것은 실제로 가리키는 값에서 소멸자가 호출되지 않기 때문에 위험합니다. 이로 인해 애플리케이션에서 메모리 / 리소스 누수가 발생할 수 있습니다.


1
char에는 생성자 / 소멸자가 없습니다.
rxantos

9

질문은 말이되지 않습니다. 당신의 혼란은 부분적으로 사람들이 자주 사용하는 엉성한 언어 때문일 수 있습니다 delete.

동적으로 할당 된 개체delete 를 삭제하는 데 사용 합니다 . 이렇게하면 해당 객체에 대한 포인터를 사용하여 삭제 표현식 을 형성 합니다 . "포인터를 삭제"하지 않습니다. 당신이 정말로하는 일은 "주소로 식별되는 객체를 삭제"하는 것입니다.

이제 왜 그 질문이 말이되지 않는지 알 수 있습니다. void 포인터는 "객체의 주소"가 아닙니다. 의미가없는 주소 일뿐입니다. 그것은 할 수있다 는 실제 개체의 주소에서왔다,하지만이로 인코딩했기 때문에 정보가 손실 유형 원래의 포인터. 객체 포인터를 복원하는 유일한 방법은 void 포인터를 객체 포인터로 다시 캐스팅하는 것입니다 (포인터가 의미하는 바를 작성자가 알아야 함). void그 자체는 불완전한 유형이므로 객체의 유형이 아니므로 void 포인터를 사용하여 객체를 식별 할 수 없습니다. (객체는 유형과 주소로 공동으로 식별됩니다.)


물론,이 질문은 주변 맥락 없이는별로 의미가 없습니다. 일부 C ++ 컴파일러는 여전히 그런 무의미한 코드를 즐겁게 컴파일합니다 (도움이된다면 경고를 짖을 수 있습니다). 따라서이 잘못된 작업을 포함하는 레거시 코드를 실행할 때 알려진 위험을 평가하기 위해 질문이 제기되었습니다. 충돌할까요? 문자 배열 메모리의 일부 또는 전부를 누출합니까? 플랫폼에 특정한 다른 것이 있습니까?
An̲̳̳

사려 깊은 답변에 감사드립니다. 찬성!
An̲̳̳

1
@Andrew : 표준은 이것에 대해 매우 분명합니다. "의 피연산자의 값은 deletenull 포인터 값, 이전 new-expression에 의해 생성 된 배열이 아닌 개체에 대한 포인터 또는 a에 대한 포인터 일 수 있습니다. 이러한 개체의 기본 클래스를 나타내는 하위 개체입니다. 그렇지 않으면 동작이 정의되지 않습니다. " 컴파일러는 진단, 그것의 아무것도하지만, 컴파일러의 버그없는 코드 ... 받아 그래서 만약
Kerrek SB

1
@KerrekSB- 컴파일러의 버그 일뿐 입니다. 동의하지 않습니다. 표준은 행동이 정의되지 않았다고 말합니다. 이것은 컴파일러 / 구현이 무엇이든 할 수 있고 여전히 표준을 준수 함을 의미합니다. 컴파일러의 응답이 void * 포인터를 삭제할 수 없다는 말이라면 괜찮습니다. 컴파일러의 응답이 하드 드라이브를 지우는 것이라면 괜찮습니다. OTOH, 컴파일러의 응답이 진단을 생성하지 않고 대신 해당 포인터와 관련된 메모리를 해제하는 코드를 생성하는 경우에도 괜찮습니다. 이것은 이러한 형태의 UB를 다루는 간단한 방법입니다.
David Hammen 2014

추가하기 위해 : 나는 delete void_pointer. 정의되지 않은 동작입니다. 프로그래머는 응답이 프로그래머가 원하는 작업을 수행하는 것처럼 보이더라도 정의되지 않은 동작을 호출해서는 안됩니다.
David Hammen 2014

6

정말로 그렇게해야한다면, 중개자 ( newdelete연산자)를 잘라 내고 글로벌 operator newoperator delete직접 전화를 거는 것은 어떨까요? (물론 newand delete연산자 를 계측하려는 경우 실제로 operator newand 를 다시 구현해야합니다 operator delete.)

void* my_alloc (size_t size)
{
   return ::operator new(size);
}

void my_free (void* ptr)
{
   ::operator delete(ptr);
}

달리 것을 참고 malloc(), operator new슬로우 std::bad_alloc실패 (또는 호출 new_handler등록되어있는 경우 하나).


char에는 생성자 / 소멸자가 없으므로 이것은 정확합니다.
rxantos

5

char에는 특별한 소멸자 논리가 없기 때문입니다. 이것은 작동하지 않습니다.

class foo
{
   ~foo() { printf("huzza"); }
}

main()
{
   foo * myFoo = new foo();
   delete ((void*)foo);
}

감독은 전화를받지 않습니다.


5

void *를 사용하려면 malloc / free 만 사용하지 않는 이유는 무엇입니까? new / delete는 단순한 메모리 관리 그 이상입니다. 기본적으로 new / delete는 생성자 / 소멸자를 호출하며 더 많은 일이 진행됩니다. 내장 유형 (예 : char *)을 사용하고 void *를 통해 삭제하면 작동하지만 여전히 권장되지 않습니다. 결론은 void *를 사용하려는 경우 malloc / free를 사용하는 것입니다. 그렇지 않으면 편의를 위해 템플릿 기능을 사용할 수 있습니다.

template<typename T>
T* my_alloc (size_t size)
{
   return new T [size];
}

template<typename T>
void my_free (T* ptr)
{
   delete [] ptr;
}

int main(void)
{
    char* pChar = my_alloc<char>(10);
    my_free(pChar);
}

이 예제에서 코드를 작성하지 않았습니다.이 패턴이 몇 군데에서 사용되어 C / C ++ 메모리 관리를 흥미롭게 혼합하고 특정 위험이 무엇인지 궁금했습니다.
An̲̳̳drew 21:35:09

C / C ++ 작성은 실패의 비결입니다. 글을 쓴 사람은 둘 중 하나를 썼어야했습니다.
David Thornley

2
@David C / C ++가 아니라 C ++입니다. C에는 템플릿이 없으며 new 및 delete를 사용하지 않습니다.
rxantos

5

많은 사람들이 이미 무효 포인터를 삭제하는 것이 안전하지 않다고 언급했습니다. 나는 그것에 동의하지만 연속 배열이나 유사한 것을 할당하기 위해 void 포인터로 작업하는 경우 안전하게 new사용할 수 있도록 이 작업을 수행 할 수 있음을 추가하고 싶습니다 delete(with, ahem , 약간의 추가 작업). 이것은 메모리 영역 ( 'arena'라고 함)에 void 포인터를 할당 한 다음 아레나에 대한 포인터를 new에 제공함으로써 수행됩니다. C ++ FAQ 의이 섹션을 참조하십시오 . 이것은 C ++에서 메모리 풀을 구현하는 일반적인 접근 방식입니다.


0

이렇게 할 이유가 거의 없습니다.

우선, 당신은 모르는 경우 유형 의 데이터를, 그리고 당신이 알고있는 모든는 점입니다 void*당신이 정말로 단지 유형이없는로 데이터를 처리해야한다, BLOB 이진 데이터 (의 unsigned char*), 사용 malloc/ free그것을 다루는 . 이것은 때때로 void*C api에 대한 포인터를 전달해야하는 파형 데이터 등과 같은 것에 필요합니다 . 괜찮아.

당신이 경우에 데이터의 유형 (즉, 그것의 ctor / dtor있다) 알고 있지만 어떤 이유로 당신은 함께 결국 void*(어떤 이유 당신이) 포인터 당신이 정말로 유형에 다시 캐스팅해야 당신이 그것을 알고 수 및 통화 delete그것에.


0

나는 코드 리플렉션과 다른 모호성의 업적 동안 프레임 워크에서 void * (알 수없는 유형)를 사용했으며 지금까지 컴파일러에서 문제 (메모리 누수, 액세스 위반 등)를 겪지 않았습니다. 비표준 작업으로 인한 경고 만 표시됩니다.

알려지지 않은 (void *)을 삭제하는 것은 완벽하게 합리적입니다. 포인터가 다음 지침을 따르는 지 확인하십시오. 그렇지 않으면 이해가되지 않을 수 있습니다.

1) 알 수없는 포인터는 사소한 해체자가있는 유형을 가리켜서는 안되므로 알 수없는 포인터로 캐스트 될 때 절대 삭제되지 않아야합니다. 알 수없는 포인터를 ORIGINAL 유형으로 다시 캐스팅 한 후에 만 ​​삭제하십시오.

2) 인스턴스가 스택 바운드 또는 힙 바운드 메모리에서 알 수없는 포인터로 참조되고 있습니까? 알 수없는 포인터가 스택의 인스턴스를 참조하는 경우 절대 삭제해서는 안됩니다!

3) 알 수없는 포인터가 유효한 메모리 영역이라고 100 % 긍정적입니까? 아니요, 그러면 절대로 삭제해서는 안됩니다!

대체로 알 수없는 (void *) 포인터 유형을 사용하여 수행 할 수있는 직접적인 작업은 거의 없습니다. 그러나 간접적으로 void *는 C ++ 개발자가 데이터 모호성이 필요할 때 의지 할 수있는 훌륭한 자산입니다.


0

버퍼 만 원하면 malloc / free를 사용하십시오. new / delete를 사용해야하는 경우 간단한 래퍼 클래스를 고려하십시오.

template<int size_ > struct size_buffer { 
  char data_[ size_]; 
  operator void*() { return (void*)&data_; }
};

typedef sized_buffer<100> OpaqueBuffer; // logical description of your sized buffer

OpaqueBuffer* ptr = new OpaqueBuffer();

delete ptr;

0

char의 특별한 경우.

char은 특수 소멸자가없는 내장 유형입니다. 따라서 유출 주장은 논쟁의 여지가 있습니다.

sizeof (char)는 일반적으로 하나이므로 정렬 인수도 없습니다. sizeof (char)가 1이 아닌 드문 플랫폼의 경우 char에 맞게 정렬 된 메모리를 할당합니다. 따라서 정렬 논쟁도 논쟁의 여지가 있습니다.

이 경우 malloc / free가 더 빠릅니다. 그러나 std :: bad_alloc을 상실하고 malloc의 결과를 확인해야합니다. 글로벌 new 및 delete 연산자를 호출하는 것이 중간 사람을 우회하므로 더 나을 수 있습니다.


1
" 는 sizeof (문자)는 일반적으로 하나 항상를 sizeof (문자) 하나입니다"
curiousguy

최근 (2019)까지 사람들은 new실제로 던지기로 정의 되었다고 생각합니다 . 이것은 사실이 아닙니다. 컴파일러 및 컴파일러 스위치에 따라 다릅니다. 예를 들어 MSVC2019 /GX[-] enable C++ EH (same as /EHsc)스위치를 참조하십시오 . 또한 임베디드 시스템에서 많은 사람들이 C ++ 예외에 대한 성능 세를 지불하지 않기로 선택합니다. 따라서 "But you forfeit std :: bad_alloc ..."로 시작하는 문장은 의심 스럽습니다.
BitTickler 19
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.