Как delete [] знает, что это массив? - PullRequest
133 голосов
/ 01 апреля 2009

Хорошо, я думаю, что мы все согласны с тем, что то, что происходит со следующим кодом, не определено, в зависимости от того, что передано,

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

Операционная система достаточно умна, чтобы удалять только одно целое число и не использовать какой-либо тип "убийственного веселья", удаляя остальную часть памяти после этой точки (в отличие от strlen и не-1011 * -определенного строка - она ​​будет продолжаться, пока не достигнет 0).

Так чья же работа - помнить эти вещи? Сохраняет ли ОС какой-либо тип записи в фоновом режиме? (Я имею в виду, я понимаю, что начал этот пост, сказав, что то, что происходит, не определено, но на самом деле сценарий «убийства» не происходит, поэтому в практическом мире кто-то помнит .)

Ответы [ 16 ]

1 голос
/ 28 января 2010

Семантически обе версии оператора удаления в C ++ могут «съесть» любой указатель; однако, если для delete[] указан указатель на один объект, то получится UB, означающее, что может произойти все, включая системный сбой или вообще ничего.

C ++ требует, чтобы программист выбрал правильную версию оператора удаления в зависимости от объекта освобождения: массив или отдельный объект.

Если компилятор может автоматически определить, является ли указатель, переданный оператору удаления, массивом указателей, тогда в C ++ будет только один оператор удаления, что будет достаточно для обоих случаев.

0 голосов
/ 08 марта 2019

«неопределенное поведение» просто означает, что языковая спецификация не дает никаких гарантий относительно того, что произойдет. Это вовсе не означает, что случится что-то плохое.

Так чья же работа - помнить эти вещи? Сохраняет ли ОС какой-либо тип записи в фоновом режиме? (Я имею в виду, я понимаю, что начал этот пост, сказав, что то, что происходит, не определено, но на самом деле сценарий «убийства» не происходит, поэтому в практическом мире кто-то вспоминает.)

Обычно здесь два слоя. Базовый менеджер памяти и реализация C ++.

Обычно диспетчер памяти запоминает (среди прочего) размер выделенного блока памяти. Это может быть больше, чем запрашиваемая реализация C ++. Обычно диспетчер памяти хранит свои метаданные перед выделенным блоком памяти.

Реализация C ++, как правило, запоминает размер массива только в том случае, если это необходимо для собственных целей, обычно потому, что тип имеет нетривальный деструктор.

Так что для типов с тривиальным деструктором реализация «delete» и «delete []» обычно одинакова. Реализация C ++ просто передает указатель на базовый менеджер памяти. Что-то вроде

free(p)

С другой стороны, для типов с нетривиальным деструктором «delete» и «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);
0 голосов
/ 08 сентября 2016

просто определите деструктор внутри класса и выполните ваш код с обоим синтаксисом

delete pointer

delete [] pointer

в зависимости от вывода вы можете найти решения

0 голосов
/ 27 января 2016

Эй, хорошо, это зависит от того, что вы выделяете с помощью выражения new [], когда вы выделяете массив типов или класс / структуру, и вы не предоставляете свой конструктор и деструктор, оператор будет обрабатывать его как размер "sizeof ( object) * numObjects ", а не массив объектов, поэтому в этом случае количество выделенных объектов не будет нигде храниться, однако, если вы выделите массив объектов и предоставите конструктор и деструктор в вашем объекте, а не изменение поведения, новое выражение выделит еще 4 байта и сохранить количество объектов в первых 4 байтах, чтобы можно было вызывать деструктор для каждого из них, и, следовательно, выражение new [] будет возвращать указатель, сдвинутый на 4 байта вперед, чем при возврате памяти выражение delete [] вызовет шаблон функции во-первых, переберите массив объектов и вызовите деструктор для каждого из них. Я создал этот простой код, который перегружает выражения 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 );
}
///////////////////////////////////////////
0 голосов
/ 27 мая 2015

Ответ:

int * pArray = new int [5];

int size = * (pArray-1);

Опубликовано выше не является правильным и дает недопустимое значение. «-1» считает элементы В 64-битной ОС Windows правильный размер буфера находится в Ptr - адрес 4 байта

0 голосов
/ 10 апреля 2009

Вы не можете использовать delete для массива, и вы не можете использовать delete [] для не-массива.

...