Как удалить [] знает массив?

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

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

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

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 и не-t27 > -термирован string - он будет продолжаться до тех пор, пока он не достигнет 0).

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

130
01 апр. '09 в 4:22
источник поделиться
15 ответов

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

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

97
01 апр. '09 в 4:30
источник

Связанные вопросы


Похожие вопросы

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

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

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

Foo* foo = new Foo;

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

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

delete[] bar;

вместо

delete bar;

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

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

100
01 апр. '09 в 4:49
источник

Да, ОС держит некоторые вещи на "фоне". Например, если вы запустите

int* num = new int[5];

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

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

26
01 апр. '09 в 4:34
источник

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

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

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

int* pArray = new int[5];
int size = *(pArray-1);
12
01 апр. '09 в 4:33
источник

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

В любом случае, смешение new/new[] и delete/delete[] возможно UB.

8
16 апр. '09 в 13:44
источник

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

6
01 апр. '09 в 4:31
источник

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

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

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

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

classes = new MyClass[3];

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

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

5
28 янв. '10 в 3:46
источник

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

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

4
01 апр. '09 в 4:32
источник

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

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

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)
4
24 февр. '14 в 12:59
источник

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

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

Для полной спецификации, когда выделено дополнительное хранилище, обратитесь к С++ ABI (как реализованы компиляторы): Itanium С++ ABI: Array Operator new Cookies

2
06 мая '11 в 12:49
источник

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

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

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

1
28 янв. '10 в 3:54
источник

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

0
10 апр. '09 в 20:18
источник

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

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

delete pointer

delete [] pointer

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

-2
08 сент. '16 в 19:56
источник

Ответ:

int * pArray = new int [5];

int size = * (pArray-1);

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

-3
27 мая '15 в 12:41
источник

Посмотрите другие вопросы по меткам или Задайте вопрос