Hacker News

Почему первое выделение C++ (m) всегда составляет 72 КБ?

Узнайте, почему при первом выделении памяти C++ требуется 72 КБ вместо ожидаемых байтов. Изучите внутреннюю структуру malloc и уровни управления памятью ОС.

1 минута чтения

Mewayz Team

Editorial Team

Hacker News

Тайна вашего первого распределения памяти в C++

Вы пишете простую программу на C++. Один новый int. Четыре байта. Вы запускаете strace или ваш любимый профилировщик памяти, и вот оно — ваш процесс только что запросил примерно 72 КБ у операционной системы. Не 4 байта. Не 64 байта. Полные 72 КБ. Если вы когда-нибудь смотрели на это число и задавались вопросом, не лгут ли вам ваши инструменты, вы не одиноки. Это, казалось бы, странное поведение — один из наиболее часто задаваемых вопросов среди разработчиков C++, впервые копающихся во внутреннем устройстве памяти, и ответ отправляет нас в увлекательное путешествие по слоям, которые находятся между вашим кодом и реальным оборудованием.

Что происходит, когда вы звоните по новому

Чтобы понять цифру в 72 КБ, нужно проследить полную цепочку распределения. Когда ваш код C++ выполняет new int, компилятор преобразует это в вызов оператора new, который в большинстве систем Linux делегирует malloc из glibc. Но malloc не запрашивает у ядра напрямую 4 байта памяти. Ядро работает со страницами — обычно 4 КБ на x86_64 — и стоимость системного вызова огромна по сравнению с простым доступом к памяти. Вызов brk() или mmap() для каждого отдельного распределения приведет к остановке любой нетривиальной программы.

Вместо этого распределитель памяти glibc — реализация под названием ptmalloc2, которая сама по себе произошла от классической dlmalloc Дуга Ли — действует как посредник. Он заранее запрашивает большие блоки памяти у ядра, а затем разрезает их на более мелкие части по мере необходимости вашей программы. Это основная причина, по которой первое выделение 4 байтов вызывает гораздо больший запрос к операционной системе. Распределитель не является расточительным. Это стратегический подход.

Анализ 72 КБ: куда идут байты

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

Во-первых, malloc в glibc инициализирует главную арену — первичную структуру учета, которая отслеживает все распределения в основном потоке. Эта область включает метаданные для кучи, указатели свободных списков и структуры ячеек для разных размеров распределения. Распределитель расширяет разрыв программы через sbrk(), а начальное расширение управляется внутренним параметром M_TOP_PAD, который по умолчанию равен 128 КБ заполнения. Однако фактический первоначальный запрос корректируется с учетом выравнивания страницы и существующей позиции разрыва, что часто приводит к меньшему размеру первого запроса — обычно в только что запущенном процессе он занимает около 72 КБ.

💡 ЗНАЕТЕ ЛИ ВЫ?

Mewayz заменяет 8+ бизнес-инструментов в одной платформе

CRM · Выставление счетов · HR · Проекты · Бронирование · eCommerce · POS · Аналитика. Бесплатный тариф доступен навсегда.

Начать бесплатно →

Во-вторых, начиная с glibc 2.26, распределитель инициализирует локальный кэш потока (tcache) при первом использовании. Tcache содержит 64 контейнера (по одному на каждый класс размера малого распределения), каждый из которых может содержать до 7 кэшированных фрагментов. Сама tcache_perthread_struct занимает около 1 КБ, но ее инициализация запускает более широкую настройку арены. В-третьих, среда выполнения C++ уже выполнила выделение еще до запуска функции main() — статические конструкторы, инициализация буфера iostream для std::cout и его друзей, а также настройка локали — все это вносит свой вклад в этот первоначальный объем кучи.

Система арены и почему предварительное распределение — это разумно

Решение заранее выделить существенный кусок памяти, а не запрашивать его по частям, не является случайностью реализации. Это преднамеренный инженерный компромисс, основанный на десятилетиях опыта системного программирования. Каждый вызов brk() или mmap() включает в себя переключение контекста из пространства пользователя в пространство ядра, изменение отображений виртуальной памяти процесса и возможные обновления таблицы страниц. На современном оборудовании один системный вызов стоит примерно 100–200 наносекунд — тривиально само по себе, но катастрофично в масштабе.

Рассмотрим программу, которая во время инициализации выполняет 10 000 небольших выделений. Без предварительного выделения это означало бы 10 000 системных вызовов, что стоило бы примерно 1–2 миллисекунды чистых накладных расходов. При использовании распределителя на основе арены первый триггер распределения

Build Your Business OS Today

From freelancers to agencies, Mewayz powers 138,000+ businesses with 207 integrated modules. Start free, upgrade when you grow.

Create Free Account →

Frequently Asked Questions

Почему именно 72 КБ, а не меньше?

Эта сумма — не случайное число. Она представляет собой начальный размер «кучи» (heap), выделяемой стандартной библиотекой C++ (часто через malloc). Этот размер выбран как компромисс: он достаточно велик, чтобы вместить множество небольших объектов и снизить количество дорогостоящих системных вызовов, но не настолько огромен, чтобы сразу же тратить много памяти впустую. Это предварительное выделение оптимизирует работу программы с самого начала.

Могу ли я изменить размер этого первого выделения?

Да, это возможно, но не стандартизировано и зависит от реализации библиотеки времени выполнения (например, glibc). Вы можете использовать специальные переменные окружения (например, `MALLOC_TOP_PAD_`) или функции для настройки аллокатора памяти. Однако делать это стоит только при наличии веских причин и глубокого понимания, так как можно ухудшить производительность. Инструменты вроде Mewayz (207 модулей, от $19/мес.) могут помочь отследить последствия таких изменений.

Это поведение одинаково для всех операционных систем и компиляторов?

Нет, конкретный размер может варьироваться. Хотя 72 КБ — распространенное значение для систем на базе glibc (многие дистрибутивы Linux), другие системы (Windows с MSVC, macOS) используют свои собственные аллокаторы с разными стратегиями и начальными размерами. Поведение зависит от реализации стандартной библиотеки C/C++, а не от самого языка C++.

Означает ли это, что моя программа тратит 72 КБ впустую?

Не совсем. Эти 72 КБ резервируются в вашем виртуальном адресном пространстве, но физическая память выделяется по мере необходимости («ленивое выделение»). Когда ваша программа реально записывает данные в эту область, ОС предоставляет физические страницы памяти. Таким образом, «тратится» в основном виртуальное адресное пространство, которого у современных 64-битных процессов более чем достаточно. Для анализа реального использования памяти полезны профилировщики, такие как Mewayz.

Попробуйте Mewayz бесплатно

Единая платформа для CRM, выставления счетов, проектов, HR и многого другого. Банковская карта не требуется.

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

Присоединяйтесь к 30,000+ компаниям. Бесплатный тариф навсегда · Без кредитной карты.

Нашли это полезным? Поделиться.

Готовы применить это на практике?

Присоединяйтесь к 30,000+ компаниям, использующим Mewayz. Бесплатный тариф навсегда — кредитная карта не требуется.

Начать бесплатный пробный период →

Готовы действовать?

Начните ваш бесплатный пробный период Mewayz сегодня

Бизнес-платформа все-в-одном. Кредитная карта не требуется.

Начать бесплатно →

14-дневный бесплатный пробный период · Без кредитной карты · Можно отменить в любой момент