Откуда C/C++ знает сколько надо освободить памяти, если не знает размер массива?

1,00
р.
В C/C++ для того чтобы обработать массив нужно знать его размер. Соответственно нужно всегда этот размер "помнить" и протаскивать во все функции обработки в качестве аргумента. Например:
void foo(int* arr, size_t n) { for (size_t i = 0 i < n i++) { arr[i] = i * i } }
Но при освобождении ресурсов знать размер почему-то необязательно. Можно просто вызвать free(arr) если память выделялась через malloc() или calloc(). Или можно использовать оператор delete[] arr если память выделялась через оператор new int[n].
Вопрос откуда C/C++ знает сколько надо освободить памяти, если не знает размер массива? Функция free() и оператор delete[] не принимают в качестве аргументов размер массива, а только указатель на массив. А если C/C++ может каким-то образом вычислить размер, то зачем его постоянно "таскать" с собой в отдельной переменной?

Ответ
Все это - детали реализации.
malloc / free В популярных реализациях malloc обычно записывает размер выделенного блока в начало выделенного блока. Возвращенный вам указатель обычно указывает на память сразу за этим записанным размером. free знает, где искать размер блока, и извлекает его именно оттуда. new / delete По умолчанию обычные new и delete (без []) просто делегируют запросы на выделение и освобождение сырой памяти в тот же самый malloc и free или их аналоги, через посредство operator new и operator delete. new[] / delete[] При работе с массивами объектов с тривиальными деструкторами new[] и delete[] фактически ведут себя точно так же: вызывают в конечном итоге malloc с правильно вычисленным общим размером массива и вызывают free для освобождения памяти. При работе с массивами объектов с нетривиальными деструкторами все несколько сложнее: new[] запрашивает от malloc немножко больше памяти и дополнительно записывает в начало выделенного блока точное количество элементов создаваемого массива, а delete[] потом извлекает это количество и вызывает правильное количество деструкторов†. Допустим, если у вас есть какой-то класс MyNonTrivialClass размером в 9 байт с нетривиальным деструктором, то выполнение MyNonTrivialClass *p = new MyNonTrivialClass[17]
приведет к формированию блока памяти со следующей внутренней структурой +-----+-----+------+------+------ | 176 | 17 | p[0] | p[1] | ... +-----+-----+------+------+------ ^ ^ ^ | | | | | p - полученный вами указатель | | | поле типа `size_t` (8 байт), записано `new[]` | поле типа `size_t` (8 байт), записано `malloc` `new[]` запросил 161 байт = 17 * 9 + 8, размер выровнен до границы 16 байт
Конкретные значения могут отличаться, но общая идея обычно в популярных реализациях именно такая. -- † Кроме нетривиальных деструкторов в языке есть еще одна ситуация, которая обычно заставляет new[] сохранять в начале выделенного блока количество элементов массива: когда создается массив объектов, содержащих перегруженный operator delete[](void *, std::size_t), т.е. функцию освобождения памяти со вторым параметром типа std::size_t. При освобождении памяти реализации обязаны передавать через этот параметр то же самое значение, которое передавалось в соответствующий вызов operator new[]. Для этого им нужно хранить точный размер массива. Что характерно, Microsoft Visual Studio по сей день (VS2019) игнорирует это требование языка, не сохраняет размер массива и передает в такой operator delete[] некорректное значение размера.
См. также https://ru.stackoverflow.com/a/770300/182825