delete []는 그것이 배열임을 어떻게 알 수 있습니까?


136

자, 우리는 전달 된 내용에 따라 다음 코드에서 발생하는 일이 정의되지 않은 것에 동의합니다.

void deleteForMe(int* pointer)
{
     delete[] pointer;
}

포인터는 모든 종류의 다른 일이 될 수 있으므로 무조건 수행하는 delete[]것은 정의되어 있지 않습니다. 그러나 실제로 배열 포인터를 전달한다고 가정 해 봅시다.

int main()
{
     int* arr = new int[5];
     deleteForMe(arr);
     return 0;
}

내 질문은,이 경우 포인터 배열 인 경우 누가 이것을 알고 있습니까? 언어 / 컴파일러의 관점에서 볼 arr때 배열 포인터와 단일 int에 대한 포인터 인지 여부는 알 수 없습니다 . 도대체 arr동적으로 생성 되었는지조차 알지 못합니다 . 그러나 대신 다음을 수행하면

int main()
{
     int* num = new int(1);
     deleteForMe(num);
     return 0;
}

OS는 단 하나 개의 INT를 삭제하고 그와 함께 (그 시점을 넘어 대조를 메모리의 나머지 부분을 삭제하여 '골라서 죽이는'의 몇 가지 유형에하지에 스마트 충분 strlen하고 비\0 문자열 조회수 0).

이 일을 기억하는 것이 누구의 일입니까? OS가 백그라운드에서 어떤 유형의 레코드를 유지합니까? (나는 무슨 일이 일어 났는지는 정의되지 않았다고 말 함으로써이 게시물을 시작했음을 알고 있지만 실제로는 '죽이는 행위'시나리오가 발생하지 않으므로 실제 세계에서 누군가 가 기억하고 있습니다.)



6
삭제 후 대괄호에서 알 수 있습니다
JoelFan

"포인터는 배열이다". 아니요, 포인터는 결코 배열이 아닙니다. 그들은 종종 배열의 첫 번째 요소를 가리 키지 만 그것은 다른 것입니다.
Aaron McDaid

답변:


99

컴파일러는 배열인지 알지 못하며 프로그래머를 신뢰합니다. 하나의 포인터를 삭제 int와 함께하는 것은 delete []정의되지 않은 동작이 발생할 것입니다. 두 번째 main()예제는 즉시 충돌하지 않더라도 안전하지 않습니다.

컴파일러는 어떻게 든 삭제해야하는 오브젝트 수를 추적해야합니다. 배열 크기를 저장하기에 충분히 초과 할당하여이를 수행 할 수 있습니다. 자세한 내용은 C ++ Super FAQ를 참조하십시오 .


14
실제로, delete []를 사용하여 new로 만든 것을 삭제하는 것은 이용할 수 있습니다. taossa.com/index.php/2007/01/03/…
로드리고

23
@Rodrigo 귀하의 의견에 대한 링크가 끊어졌지만, 고맙게도 wayback 기계는 replay.web.archive.org/20080703153358/http://taossa.com/에
David Gardner

103

지금까지 주어진 답변으로 해결되지 않는 한 가지 질문 : 런타임 라이브러리 (OS가 아닌)가 배열의 수를 추적 할 수 있다면 왜 delete[]구문이 필요한가? 왜 싱글을 할 수 없습니까delete모든 삭제를 처리하는 데 양식을 사용할 입니까?

이에 대한 답은 C 호환 언어 인 C ++의 근본으로 거슬러 올라갑니다 (더 이상 실제로는 노력하지 않습니다) Stroustrup의 철학은 프로그래머가 사용하지 않는 기능에 대해 프로그래머가 비용을 지불 할 필요가 없다는 것입니다. 배열을 사용하지 않는 경우 할당 된 모든 메모리 청크에 대해 개체 배열 비용을 부담하지 않아도됩니다.

즉, 코드가 단순히

Foo* foo = new Foo;

할당 된 메모리 공간은의 foo배열을 지원하는 데 필요한 추가 오버 헤드를 포함하지 않아야합니다 Foo.

추가 배열 크기 정보를 전달하도록 배열 할당 만 설정되었으므로 오브젝트를 삭제할 때 해당 정보를 찾도록 런타임 라이브러리에 지시해야합니다. 우리가 사용해야하는 이유

delete[] bar;

그냥 대신

delete bar;

bar가 배열에 대한 포인터 인 경우

우리 대부분은 (나 자신을 포함하여), 몇 바이트의 메모리에 대한 까다로운 점은 요즘 기이 한 것 같습니다. 그러나 (아직 많은 수의 메모리 블록이 될 수있는 것에서) 몇 바이트를 저장하는 것이 중요한 상황이 여전히 있습니다.


20
"요즘 여분의 여분의 바이트 메모리에 대한 혼란은 기이 한 것 같다". 다행스럽게도, 그런 사람들에게 맨손 배열도 기이하게 보이기 시작합니다. 따라서 벡터 또는 boost :: array를 사용하고 delete [] forever :-)를 잊어 버릴 수 있습니다.
Steve Jessop

28

예, OS는 '배경'에 몇 가지 사항을 유지합니다. 예를 들어

int* num = new int[5];

OS는 4 개의 추가 바이트를 할당하고 할당 된 메모리의 처음 4 바이트에 할당 크기를 저장하고 오프셋 포인터를 반환합니다 (즉, 메모리 공간을 1000에서 1024까지 할당하지만 포인터는 1004를 가리킴). 할당의 크기를 저장하는 1003). 그런 다음 delete가 호출되면 포인터가 전달되기 전에 4 바이트를보고 할당 크기를 찾을 수 있습니다.

할당 크기를 추적하는 다른 방법이 있다고 확신하지만 이것이 하나의 옵션입니다.


26
+1-일반적으로 언어 런타임이 OS가 아닌이 메타 데이터를 저장하는 것을 제외하고는 일반적으로 유효한 지점입니다.
sharptooth

배열의 크기 또는 배열이 정의 된 객체의 크기는 어떻게됩니까? 해당 객체에서 sizeof를 수행 할 때 추가 4 바이트가 표시됩니까?
Shree

3
아니요, sizeof는 배열의 크기 만 보여줍니다. 런타임이 내가 설명한 방법으로 구현하기로 선택한 경우 엄밀히 구현 세부 사항이며 사용자의 관점에서 마스킹해야합니다. 포인터 앞의 메모리는 사용자에게 '포함되지'않으며 계산되지 않습니다.
bsdfish 2016 년

2
더 중요한 것은 sizeof는 어떤 경우에도 동적으로 할당 된 배열의 실제 크기를 반환하지 않습니다. 컴파일 타임에 알려진 크기 만 반환 할 수 있습니다.
bdonlan

for 루프에서이 메타 데이터를 사용하여 어레이를 정확하게 반복 할 수 있습니까? 예 for(int i = 0; i < *(arrayPointer - 1); i++){ }
Sam

13

이것은 질문과 매우 유사하며 찾고있는 세부 사항이 많이 있습니다.

그러나 이것으로 추적하는 것은 OS의 일이 아닙니다. 실제로 런타임 라이브러리 또는 배열의 크기를 추적하는 기본 메모리 관리자입니다. 일반적으로 추가 메모리를 미리 할당하고 해당 위치에 배열 크기를 저장하면됩니다 (대부분 헤드 노드 사용).

이것은 다음 코드를 실행하여 일부 구현에서 볼 수 있습니다

int* pArray = new int[5];
int size = *(pArray-1);

이게 효과가 있을까요? Windows 및 Linux에서는이 작업을 수행하지 못했습니다.
친구

1
size_t size = *(reinterpret_cast<size_t *>(pArray) - 1)대신 시도

9

delete또는 delete[]할당 된 메모리 (메모리가 지적한)를 해제 할 수 있지만 가장 큰 차이점은 delete배열에서 배열의 각 요소의 소멸자를 호출하지 않는다는 것입니다.

어쨌든, 혼합 new/new[]하고 delete/delete[]아마도 UB입니다.


1
명확하고 짧고 가장 유용한 답변!
GntS

6

배열인지 알 수 없으므로 delete[]일반 old 대신 공급해야합니다 delete.


5

나는 이것과 비슷한 질문을했다. C에서는 malloc () (또는 다른 유사한 함수)을 사용하여 메모리를 할당하고 free ()로 삭제하십시오. malloc ()이 하나만 있는데, 이는 단순히 특정 바이트 수를 할당합니다. 하나의 free () 만 있으며, 매개 변수로 포인터를 가져옵니다.

그렇다면 왜 C에서 포인터를 free로 넘길 수는 있지만 C ++에서는 배열인지 단일 변수인지 알려 주어야합니까?

내가 배운 대답은 클래스 소멸자와 관련이 있습니다.

MyClass 클래스의 인스턴스를 할당하면 ...

classes = new MyClass[3];

삭제로 삭제하면 MyClass의 첫 번째 인스턴스에 대한 소멸자 만 호출 할 수 있습니다. delete []를 사용하면 배열의 모든 인스턴스에 대해 소멸자가 호출됩니다.

이것이 중요한 차이점입니다. 단순히 표준 유형 (예 : int)으로 작업하는 경우 실제로이 문제가 표시되지 않습니다. 또한 new [] 및 delete []에서 delete를 사용하는 동작은 정의되어 있지 않으므로 모든 컴파일러 / 시스템에서 동일한 방식으로 작동하지 않을 수 있습니다.


3

free를 사용하여 표준 C에서 malloc으로 작성된 배열을 삭제할 수있는 것과 동일한 방식으로 메모리 할당을 담당하는 런타임에 달려 있습니다. 각 컴파일러는 다르게 구현한다고 생각합니다. 한 가지 일반적인 방법은 배열 크기에 추가 셀을 할당하는 것입니다.

그러나 런타임은 배열 또는 포인터인지 여부를 감지하기에 충분히 똑똑하지 않으며, 알려 주어야하며, 실수하면 올바르게 삭제하지 않습니다 (예 : 배열 대신 ptr). 크기와 관련이없는 값을 가져와 심각한 손상을 초래할 수 있습니다.


3

컴파일러의 접근 방법 중 하나는 헤드 요소에 약간의 메모리를 할당하고 요소 수를 저장하는 것입니다.

수행 방법 예 : 여기

int* i = new int[4];

컴파일러는 sizeof (int) * 5 바이트를 할당합니다.

int *temp = malloc(sizeof(int)*5)

4sizeof(int)바이트에 저장 됩니다

*temp = 4;

그리고 설정 i

i = temp + 1;

따라서 i5가 아닌 4 요소의 배열을 가리 킵니다.

delete[] i;

다음과 같이 처리됩니다

int *temp = i - 1;
int numbers_of_element = *temp; // = 4
... call destructor for numbers_of_element elements if needed
... that are stored in temp + 1, temp + 2, ... temp + 4
free (temp)

1

의미 적으로, C ++에서 두 버전의 delete 연산자는 모든 포인터를 "먹을"수 있습니다. 그러나 단일 객체에 대한 포인터가delete[] UB가 발생하여 시스템 충돌 또는 전혀 발생하지 않는 모든 일이 발생할 수 있습니다.

C ++은 프로그래머가 할당 해제의 주제에 따라 적절한 버전의 삭제 연산자를 선택하도록 요구합니다 : 배열 또는 단일 객체.

컴파일러가 delete 연산자로 전달 된 포인터가 포인터 배열인지 여부를 자동으로 판별 할 수 있으면 C ++에는 단 하나의 delete 연산자 만 있으면됩니다. 두 경우 모두에 충분합니다.


1

컴파일러가 배열인지 여부를 알지 못한다는 데 동의하십시오. 프로그래머에게 달려 있습니다.

컴파일러는 때때로 배열 크기를 저장할만큼 충분히 할당하여 삭제해야하는 객체 수를 추적하지만 항상 필요한 것은 아닙니다.

추가 스토리지가 할당 될 때의 전체 사양은 C ++ ABI (컴파일러 구현 방법)를 참조하십시오. Itanium C ++ ABI : Array Operator new Cookies


난 단지 모든 컴파일러가 관찰 할 몇 가지 C에 대한 문서화 된 ABI를 ++. 내가 방문한 링크에 +1 감사.
돈 웨이크 필드

0

배열에는 delete 를 사용할 수 없으며 비 배열에는 delete [] 를 사용할 수 없습니다 .


8
평범한 컴파일러가 남용을 감지하지 않기 때문에 그렇게 해서는 안된다고 생각합니다 .
돈 웨이크 필드

0

"정의되지 않은 행동"은 단순히 언어 사양이 어떤 일이 발생하는지 보증하지 않음을 의미합니다. 그렇다고 나쁜 일이 일어날 것이라는 의미는 아닙니다.

이 일을 기억하는 것이 누구의 일입니까? OS가 백그라운드에서 어떤 유형의 레코드를 유지합니까? (나는 무슨 일이 일어 났는지는 정의되지 않았다고 말 함으로써이 게시물을 시작했다는 것을 알고 있지만 실제로는 '죽이는 행위'시나리오가 발생하지 않으므로 실제 세계에서 누군가가 기억하고 있습니다.)

여기에는 일반적으로 두 개의 레이어가 있습니다. 기본 메모리 관리자 및 C ++ 구현

일반적으로 메모리 관리자는 할당 된 메모리 블록의 크기를 기억합니다. 이것은 C ++ 구현이 요청한 블록보다 클 수 있습니다. 일반적으로 메모리 관리자는 할당 된 메모리 블록 앞에 메타 데이터를 저장합니다.

C ++ 구현은 일반적으로 자체 목적을 위해 필요한 경우 배열의 크기 만 기억합니다. 일반적으로 유형에 트라이 벌이 아닌 소멸자가 있기 때문입니다.

따라서 간단한 소멸자를 가진 유형의 경우 "delete"와 "delete []"의 구현은 일반적으로 동일합니다. C ++ 구현은 단순히 기본 메모리 관리자에 포인터를 전달합니다. 같은 것

free(p)

반면에 소멸자가 아닌 유형의 경우 "delete"와 "delete []"가 다를 수 있습니다. "삭제"는 (T는 포인터가 가리키는 유형입니다.)

p->~T();
free(p);

"delete []"는 다음과 같습니다.

size_t * pcount = ((size_t *)p)-1;
size_t count = *count;
for (size_t i=0;i<count;i++) {
  p[i].~T();
}
char * pmemblock = ((char *)p) - max(sizeof(size_t),alignof(T));
free(pmemblock);

-1

객체 배열을 반복하고 각각에 대해 소멸자를 호출합니다. 이 간단한 코드 스위치는 new [] 및 delete [] 표현식을 오버로드하고 필요한 경우 메모리 할당을 해제하고 각 객체에 대해 소멸자를 호출하는 템플릿 함수를 제공합니다.

// overloaded new expression 
void* operator new[]( size_t size )
{
    // allocate 4 bytes more see comment below 
    int* ptr = (int*)malloc( size + 4 );

    // set value stored at address to 0 
    // and shift pointer by 4 bytes to avoid situation that
    // might arise where two memory blocks 
    // are adjacent and non-zero
    *ptr = 0;
    ++ptr; 

    return ptr;
}
//////////////////////////////////////////

// overloaded delete expression 
void static operator delete[]( void* ptr )
{
    // decrement value of pointer to get the
    // "Real Pointer Value"
    int* realPtr = (int*)ptr;
    --realPtr;

    free( realPtr );
}
//////////////////////////////////////////

// Template used to call destructor if needed 
// and call appropriate delete 
template<class T>
void Deallocate( T* ptr )
{
    int* instanceCount = (int*)ptr;
    --instanceCount;

    if(*instanceCount > 0) // if larger than 0 array is being deleted
    {
        // call destructor for each object
        for(int i = 0; i < *instanceCount; i++)
        {
            ptr[i].~T();
        }
        // call delete passing instance count witch points
        // to begin of array memory 
        ::operator delete[]( instanceCount );
    }
    else
    {
        // single instance deleted call destructor
        // and delete passing ptr
        ptr->~T();
        ::operator delete[]( ptr );
    }
}

// Replace calls to new and delete
#define MyNew ::new
#define MyDelete(ptr) Deallocate(ptr)

// structure with constructor/ destructor
struct StructureOne
{
    StructureOne():
    someInt(0)
    {}
    ~StructureOne() 
    {
        someInt = 0;
    }

    int someInt;
};
//////////////////////////////

// structure without constructor/ destructor
struct StructureTwo
{
    int someInt;
};
//////////////////////////////


void main(void)
{
    const unsigned int numElements = 30;

    StructureOne* structOne = nullptr;
    StructureTwo* structTwo = nullptr;
    int* basicType = nullptr;
    size_t ArraySize = 0;

/**********************************************************************/
    // basic type array 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( int ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor. value assigned to basicType pointer
    // will be the same as value of "++ptr" in new expression
    basicType = MyNew int[numElements];

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( int ) * numElements"
    MyDelete( basicType );

/**********************************************************************/
    // structure without constructor and destructor array 

    // behavior will be the same as with basic type 

    // place break point here and in new expression
    // check size and compare it with size passed 
    // in to new expression size will be the same
    ArraySize = sizeof( StructureTwo ) * numElements;

    // this will be treated as size rather than object array as there is no 
    // constructor and destructor value assigned to structTwo pointer
    // will be the same as value of "++ptr" in new expression
    structTwo = MyNew StructureTwo[numElements]; 

    // Place break point in template function to see the behavior
    // destructors will not be called and it will be treated as 
    // single instance of size equal to "sizeof( StructureTwo ) * numElements"
    MyDelete( structTwo );

/**********************************************************************/
    // structure with constructor and destructor array 

    // place break point check size and compare it with size passed in
    // new expression size in expression will be larger by 4 bytes
    ArraySize = sizeof( StructureOne ) * numElements;

    // value assigned to "structOne pointer" will be different 
    // of "++ptr" in new expression  "shifted by another 4 bytes"
    structOne = MyNew StructureOne[numElements];

    // Place break point in template function to see the behavior
    // destructors will be called for each array object 
    MyDelete( structOne );
}
///////////////////////////////////////////

-2

클래스 내에서 소멸자를 정의하고 두 구문 모두로 코드를 실행하십시오.

delete pointer

delete [] pointer

출력 u에 따르면 솔루션을 찾을 수 있습니다


배열 유형을 새로 만들 때 delete []를 사용하십시오. 예를 들어 int * a = new int; int * b = 새로운 int [5]; 삭제 삭제 [] b;
Lineesh K Mohan

-3

대답:

int * pArray = 새로운 int [5];

int 크기 = * (pArray-1);

위에 게시 된 내용이 올바르지 않으며 잘못된 값을 생성합니다. "-1"은 요소를 계산합니다. 64 비트 Windows OS에서 올바른 버퍼 크기는 Ptr-4 바이트 주소에 있습니다.

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