Ответ
Оператор new в C++ не «понимает» размер динамически. Размер вычисляется на этапе компиляции на основе типа и передается в низкоуровневую функцию выделения памяти.
Механизм работы new T:
- Компилятор вычисляет
sizeof(T)с учетом выравнивания (alignof(T)). - Генерируется вызов глобальной функции
void* operator new(std::size_t count), куда передается вычисленный размер. - Эта функция запрашивает
countбайт у менеджера памяти ОС (например, черезmallocили системный вызов). - Если тип
Tимеет нетривиальный конструктор, компилятор затем вызывает его для инициализации памяти.
Пример для массива new T[n]:
MyClass* arr = new MyClass[10];
Компилятор вычислит общий размер как:
total_size = sizeof(MyClass) * 10 + возможный_оверхед.
Оверхед может потребоваться для хранения размера массива (чтобы delete[] знал, сколько деструкторов вызвать).
На практике:
int* p1 = new int; // Выделяет sizeof(int) байт (например, 4)
double* p2 = new double[5]; // Выделяет 5 * sizeof(double) байт (например, 5*8=40)
struct alignas(16) Vec4 { float x, y, z, w; };
Vec4* vec = new Vec4; // Выделяет 16 байт с выравниванием 16
Важные детали:
- Размер известен компилятору:
sizeof— это оператор времени компиляции. - Выравнивание: Память выделяется с учетом
alignof(T). Неправильное выравнивание может привести к падению производительности (на x86) или к ошибке (на ARM). - Placement new:
new (buffer) T()— не выделяет память, а только конструирует объект в предоставленном буфереbuffer. Размер типа все равно известен компилятору. - Пользовательский
operator new: Можно переопределить, чтобы управлять выделением, но ему все равно передается готовый размер.
Ответ 18+ 🔞
Давай разберём эту тему, а то некоторые думают, что оператор new — это такой магический ящик, который сам всё знает. Хуй с горы! Всё куда прозаичнее.
Вот смотри, когда ты пишешь new T, компилятор — этот хитрая жопа — не бегает в рантайме с линейкой, засовывая её в каждый тип. Нет, ёпта! Он всё вычисляет на этапе компиляции, пока ты кофе пьёшь. Он смотрит на T, берёт оператор sizeof(T), учитывает выравнивание (alignof(T)), получает число байтов и — хуй! — передаёт это число в низкоуровневую функцию выделения памяти. Всё, овердохуища магии.
Как это работает по шагам, если грубо:
- Компилятор: «Так,
T.sizeof(T)будет столько-то,alignof(T)— столько. Складываем, получаемcount». - Компилятор генерирует вызов: «Эй,
operator new, на, выделиcountбайтов, вот тебе размер!». - Функция
operator newидёт к менеджеру памяти ОС (чаще всего черезmalloc) и говорит: «Дашьcountбайтов?». - Если у
Tесть конструктор (нетривиальный), компилятор потом дёргает его, чтобы проинициализировать эту свежевыделенную память. Без этого — просто кусок сырой памяти.
Пример с массивом, чтобы вообще всё стало ясно:
MyClass* arr = new MyClass[10];
Что делает компилятор? Он считает: общий_размер = sizeof(MyClass) * 10. Но часто ещё приплюсовывает возможный оверхед — несколько лишних байтов, чтобы delete[] потом знал, сколько объектов разрушать. А то как он иначе узнает, насколько далеко прыгать, вызывая деструкторы? Вот именно, ни хуя себе!
Практика, чтобы закрепить:
int* p1 = new int; // Выделит sizeof(int) байт. Скорее всего, 4.
double* p2 = new double[5]; // Выделит 5 * sizeof(double). Допустим, 5*8=40.
struct alignas(16) Vec4 { float x, y, z, w; }; // Специальное выравнивание!
Vec4* vec = new Vec4; // Выделит 16 байт, и адрес будет кратен 16. Иначе на некоторых процессорах будет пиздец производительности или вообще ошибка.
Важные детали, которые надо помнить, чтобы не выглядеть ебаньком:
- Размер известен на этапе компиляции:
sizeof— это не функция, а оператор. Всё решается, когда код компилируется. Никакой магии в рантайме. - Выравнивание — важно! Память выделяется с учётом
alignof(T). Если проигнорировать, на x86 просто будешь терять производительность, а на ARM можешь получить мгновенный краш. Хуй моржовый! - Placement new — особый случай:
new (buffer) T()— это вообще не про выделение памяти. Это команда: «Вот тут, в готовом буфереbuffer, позови конструктор дляT». Размер типа компилятор и тут знает, просто память ему уже дали. - Можно переопределить
operator new: Да, ты можешь написать свою функцию, которая будет выделять память. Но ей всё равно передадут готовый, вычисленный компилятором размер. Твоя задача — просто найти столько байтов и вернуть указатель. Доверия к тому, что ты сделаешь это правильно, ебать ноль, но возможность есть.
Короче, new — это не волшебник, а просто посредник. Вся арифметика делается до запуска программы. Э бошка думай!