Как delete [] знает, что это массив?



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



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



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

692   15  

15 ответов:

компилятор не знает, что это массив, он доверяет программисту. Удаление указателя на один 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-1003, хранящими размер выделения). Затем, когда вызывается delete, он может посмотреть на 4 байта до того, как указатель будет передан ему, чтобы найти размер распределения.

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

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

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

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

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

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

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

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

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

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

ответ, я узнал, есть чтобы сделать с деструкторами классов.

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

classes = new MyClass[3];

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

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

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

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

один из подходов для компиляторов-выделить немного больше памяти и сохранить количество элементов в головном элементе.

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

int* i = new int[4];

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

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

хранит 4 в первом sizeof(int) байт

*temp = 4;

и set 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++ ABI (как реализуются компиляторы):Itanium C++ ABI: оператор массива новые Cookies

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

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

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

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

Hey ho ну это зависит от того, что вы выделяете с новым выражением[] когда вы выделяете массив сборки в типах или классе / структуре, и вы не предоставляете свой конструктор и деструктор оператор будет рассматривать его как размер "sizeof (object)*numObjects", а не массив объектов, поэтому в этом случае количество выделенных объектов не будет храниться нигде, однако если вы выделяете массив объектов и предоставляете конструктор и деструктор в своем объекте, чем изменение поведения, новое выражение выделит еще 4 байта и сохранит количество объектов в первых 4 байтах, поэтому деструктор для каждого из них может быть вызван, и поэтому новое выражение[] вернет указатель, сдвинутый на 4 байта вперед, чем при возврате памяти выражение 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

согласно выходу u может найти решения

ответ:

int * pArray = new int[5];

int size = *(pArray-1);

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

Comments

    Ничего не найдено.