Вопрос

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

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

ОС достаточно умна, чтобы удалить только один int и не продолжать какую-то «убийственную серию», удаляя остальную часть памяти после этой точки (сравните это с strlen и не-\0-завершенная строка — она будет продолжаться, пока не достигнет 0).

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

Это было полезно?

Решение

Компилятор не знает, что это массив, он доверяет программисту.Удаление указателя на одиночный int с delete [] приведет к неопределенному поведению.Твой второй main() пример небезопасен, даже если он не выйдет из строя сразу.

Компилятору приходится каким-то образом отслеживать, сколько объектов необходимо удалить.Это может быть сделано путем избыточного выделения достаточного количества памяти для хранения размера массива.Более подробную информацию см. Супер часто задаваемые вопросы по C++.

Другие советы

Один вопрос, на который ответы, данные до сих пор, похоже, не затрагивают:если библиотеки времени выполнения (на самом деле, а не ОС) могут отслеживать количество элементов в массиве, то зачем нам delete[] синтаксис вообще?Почему ни один не может delete форма будет использоваться для обработки всех удалений?

Ответ на этот вопрос восходит к корням C++ как C-совместимого языка (к чему он больше не стремится). Философия Страуструпа заключалась в том, что программисту не нужно платить за какие-либо функции, которые он не использует.Если они не используют массивы, им не придется нести стоимость объектных массивов для каждого выделенного фрагмента памяти.

То есть, если ваш код просто выполняет

Foo* foo = new Foo;

затем пространство памяти, выделенное для foo не должен включать в себя какие-либо дополнительные накладные расходы, необходимые для поддержки массивов Foo.

Поскольку для переноса дополнительной информации о размере массива настроены только выделения массива, вам необходимо указать библиотекам времени выполнения искать эту информацию при удалении объектов.Вот почему нам нужно использовать

delete[] bar;

вместо того, чтобы просто

delete bar;

если bar является указателем на массив.

Большинству из нас (включая меня) эта суета по поводу нескольких дополнительных байтов памяти в наши дни кажется странной.Но все же существуют ситуации, когда сохранение нескольких байтов (из очень большого количества блоков памяти) может оказаться важным.

Да, ОС сохраняет некоторые вещи на «фоне». Например, если вы запускаете

int* num = new int[5];

ОС может выделить 4 дополнительных байта, сохранить размер выделения в первых 4 байтах выделенной памяти и вернуть указатель смещения (т. е. она выделяет области памяти от 1000 до 1024, но возвращаемый указатель указывает на 1004 с местоположениями 1000–1000–1000). 1003, сохраняющий размер выделения).Затем, когда вызывается метод delete, он может просмотреть 4 байта до того, как ему будет передан указатель, чтобы определить размер выделения.

Я уверен, что существуют и другие способы отслеживания размера распределения, но это один из вариантов.

Это очень похоже на этот вопрос, и в нем есть много деталей, которые вы ищете.

Но достаточно сказать, что в задачу ОС не входит отслеживание всего этого.На самом деле размер массива отслеживают библиотеки времени выполнения или базовый диспетчер памяти.Обычно это делается путем предварительного выделения дополнительной памяти и сохранения размера массива в этом месте (большинство из них используют головной узел).

В некоторых реализациях это можно просмотреть, выполнив следующий код

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

delete или delete[] вероятно, освободит выделенную память (указанную память), но большая разница в том, что delete в массиве не будет вызывать деструктор каждого элемента массива.

В любом случае, смешивание new/new[] и delete/delete[] вероятно, это УБ.

Он не знает, что это массив, поэтому вам нужно предоставить delete[] вместо обычного старого delete.

У меня был аналогичный вопрос.В C вы выделяете память с помощью malloc() (или другой подобной функции) и удаляете ее с помощью free().Существует только одна функция malloc(), которая просто выделяет определенное количество байтов.Существует только одна функция free(), которая просто принимает указатель в качестве параметра.

Так почему же в C вы можете просто передать указатель на free, а в C++ вы должны указать, является ли это массивом или одной переменной?

Ответ, как я понял, связан с деструкторами классов.

Если вы выделяете экземпляр класса MyClass...

classes = new MyClass[3];

И удалите его с помощью delete, вы можете получить деструктор только для первого вызванного экземпляра MyClass.Если вы используете delete[], вы можете быть уверены, что деструктор будет вызван для всех экземпляров массива.

ЭТО важное отличие.Если вы просто работаете со стандартными типами (например,int), вы действительно не увидите эту проблему.Кроме того, вы должны помнить, что поведение использования delete в new[] и delete[] в new не определено — оно может работать по-разному в каждом компиляторе/системе.

За распределение памяти отвечает среда выполнения, точно так же, как вы можете удалить массив, созданный с помощью malloc в стандарте C, используя free.Я думаю, что каждый компилятор реализует это по-своему.Один из распространенных способов — выделить дополнительную ячейку для размера массива.

Однако среда выполнения недостаточно умна, чтобы определить, является ли это массивом или указателем, вам нужно сообщить об этом, и если вы ошибаетесь, вы либо удаляете неправильно (например, ptr вместо массива), либо в конечном итоге вы принимаете несвязанное значение размера и наносите значительный ущерб.

ОДИН ИЗ подходов к компиляторам — выделить немного больше памяти и сохранить количество элементов в головном элементе.

Пример того, как это можно сделать:Здесь

int* i = new int[4];

компилятор выделит sizeof(int)*5 байт.

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

буду хранить 4 во-первых sizeof(int) байты

*temp = 4;

и установить i

i = temp + 1;

Так i указывает на массив из 4 элементов, а не из 5.

И

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)

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

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

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

Согласитесь, что компилятор не знает, массив это или нет.Это зависит от программиста.

Компилятор иногда отслеживает, сколько объектов необходимо удалить, выделяя достаточно места для хранения размера массива, но это не всегда необходимо.

Полную информацию о выделении дополнительной памяти см. в C++ ABI (как реализованы компиляторы): Itanium C++ ABI:Новые файлы cookie оператора массива

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

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

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

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

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

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

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

free(p)

С другой стороны, для типов с нетривиальным деструктором «delete» и «delete []», скорее всего, будут разными.«удалить» будет что-то вроде (где T — тип, на который указывает указатель)

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

А «удалить []» будет что-то вроде.

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

Эй, ну, это зависит от того, что вы выделяете с помощью выражения 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 );
}
///////////////////////////////////////////

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

delete pointer

delete [] pointer

по выходным данным вы можете найти решения

Ответ:

int* pArray = новый int[5];

размер int = *(pArray-1);

Сообщение выше неверно и выдает недопустимое значение.Элементы «-1» подсчитывают на 64 -битной ОС Windows. Правильный размер буфера содержится в адресе PTR - 4 байта

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top