Страницы

Поиск по вопросам

пятница, 29 ноября 2019 г.

Откуда оператор delete[] узнает сколько байт следует освободить

#c++


Что-то я задумался о том, что не понимаю одной вроде тривиальной вещи.

int *p = new int[20];
delete[] p;


Откуда оператор delete[] узнает о том, что нужно освободить именно 20 * sizeof(int) байт?

Что произойдет в результате выполнения следующего кода:

#include 
using namespace std;
int main()
{
    int *p1 = new int[16];
    int *p2 = p1;
    p1 = new int[20];
    cout << p1 << " --- " << p2 << " --- " << p1 - p2 << endl;
    delete[] p1;
    delete[] p2;
    return 0;
}


и корректен ли он вообще?
Вывод на консоль: 

0x3d2b30 --- 0x3d2ae8 --- 18


Т.е. между выделенными областями памяти есть еще 64 разряда. Последнее число всегда
четное, и минимум на 2 больше, чем размер первого массива.

Еще один эксперимент (выше было на Win7/Qt/mingw), теперь Ubunto14.04/Qt/g++:

int main()
{
    int *p1 = new int[20];
    int *p2 = p1;
    p1 = new int[20];
    int i;
    for (i=0; i<20; i++) {
        p1[i] = 0xAAAAAAAA;
        p2[i] = 0xFFFFFFFF;
    }
    cout << "Pointers: " << p1 << " --- " << p2 << " --- " << p1 - p2 << endl;
    cout << "shifted pinters: " << p1 - 1 << " --- " << p1 - 2 << endl;
    cout << "values in skipped space: " << hex << *(p1-1) << " --- " << *(p1-2)
                                    << " --- " << *(p1-3) << " --- " << *(p1-4) << endl;
    cout << "values from p2 memory: " << hex << *(p1-5) << " --- " << *(p1-6)
                                    << " --- " << *(p1-7) << " --- " << *(p1-8) << endl;
    delete[] p1;
    delete[] p2;
    return 0;
}


Вывод на консоль:

Pointers: 0x1ebd070 --- 0x1ebd010 --- 24
shifted pinters: 0x1ebd06c --- 0x1ebd068
values in skipped space: 0 --- 61 --- 0 --- 0
values from p2 memory: ffffffff --- ffffffff --- ffffffff --- ffffffff

    


Ответы

Ответ 1



Оператор "new с квадратными скобками" сохраняет информацию о количестве элементов массива. delete[] достает эту информацию и вызывает деструкторы у элементов. Компилятор может сгенерировать код для этих операторов следующим образом: // Исходный код struct A { A(); ~A(); }; A* a = new A[10]; delete[] a; //--------------------------------------------------------------- // Код, который генерирует компилятор (см. примечения ниже) // A* a = new A[10]; A* a; { // выделяем память void* _mem = malloc(sizeof(int) + 10 * sizeof(A)); // ^- выделяем дополнительную память для размера массива int* _size_ptr = (int*)_mem; *_size_ptr = 10; // сохраняем размер A* a = (A*)&_size_ptr[1]; // "a" указывает на память за сохраненным размером массива for (int i = 0; i != 10; ++i) ::operator new(a + i) A; // вызываем конструкторы } // delete[] a; { // перемещаем указатель на начало выделенной памяти int* _size_ptr = (int*)a - 1; for (int i = *_size_ptr - 1; i >= 0; --i) a[i].~A(); // вызываем деструкторы // удаляем память free(_size_ptr); } Примечание1: На самом деле вместо malloc и free вызываются функции void* operator new[](size_t bytes) и void operator delete[](void*). Но т.к. они вызывают что-то похожее на malloc/free, то в контексте данного вопроса этим можно пренебречь. Примечание2: Для обработки исключений, компилятор будет генерировать try-catch блок для new[].

Ответ 2



Когда вы выделяете память к куче, аллокатор знает, сколько памяти было выделено. Эта информация находится в "голове" сегмента перед самой информацией. Когда необходимо очистить память, деалокатор берёт эту мета-информацию и удаляет данные. Хотя, это вроде как зависит от компилятора. Надо читать документацию. По поводу же второй части не совсем уверен, давно с C++ не работал, но проблемы не вижу, вроде всё корректно: int *p1 = new int[30]; // выделяется 30 элементов, указатель p1 ссылается на эту область int *p2 = p1; // указатель p2 ссылается на ту же область памяти p1 = new int[20]; // выделяется 20 элементов, указатель p1 теперь ссылается на эту область delete[] p1; // удаляем 20 элементов delete[] p2; // удаляем 30 элементов

Комментариев нет:

Отправить комментарий