Каков "правильный" способ согласования malloc и new в смешанной программе C/С++?
У меня есть смешанная программа на C/С++. Он содержит синтаксический анализатор flex/bison, который нацелен на C, а остальная часть - С++.
Будучи C, сгенерированный анализатор и сканер управляют своей памятью с помощью malloc
, realloc
и free
. Они достаточно хороши, чтобы выставлять крючки, позволяющие мне представить свои собственные реализации этих функций. Как и следовало ожидать, остальная часть программы (С++) "хочет" использовать new
, delete
и т.д.
Выполнение небольшого исследования, похоже, показывает, что соответствующие стандарты не гарантируют, что такое смешивание должно работать. В частности, C "куча" не обязательно является свободной областью С++. Кажется, что две схемы могут попирать друг друга.
Кроме того, когда-нибудь (скоро) эта программа, вероятно, захочет интегрировать индивидуальную реализацию кучи, такую как tcmalloc, используемая и C и С++.
Что такое "правильная" вещь здесь?
Учитывая желание интегрировать tcmalloc (который объясняет, как связываться с C-программами), я испытываю соблазн найти кросс-тип, кросс-нить, кросс-все перегрузку/крюк/что-то в управлении памятью С++. С этим я мог бы указать все вызовы выделения/релиза С++ обратно на их C-эквиваленты (которые, в свою очередь, приземляются на tcmalloc.)
Существует ли такой пангалактический глобальный С++-крючок? Возможно, он уже делает то, что я хочу, подобно тому, как ios_base::sync_with_stdio
по умолчанию закрывает iostream и stdio?
Мне не интересно говорить о stdio vs. iostreams, а также о переключении генераторов парсера и использовании скелетов С++ flex/bison (они вводят независимые головные боли.)
EDIT. Пожалуйста, укажите имена тех разделов стандарта С++, которые поддерживают ваш ответ.
Ответы
Ответ 1
Стандарт гарантирует совместимость двух вариантов распределения. То, что он не позволяет, это такие вещи, как вызов free
в памяти, который пришел из new
, поскольку они могут использовать совершенно другую арену для двух типов.
Предоставляя вам помнить, что вы вызываете правильную функцию освобождения для заданного блока памяти, вы будете в порядке. Они не будут топтать друг друга, если вы будете следовать правилам и, если вы не будете следовать правилам, тогда, технически, вы делаете топтание, а не их: -)
Контрольная часть стандарта С++ 11 - это 20.6.13 C library
, которая заявляет, перефразируя:
- Функции
calloc
, malloc
, free
и realloc
предоставляются на основе стандарта C.
- Функции не используют
::operator new()
или ::operator delete()
.
- Это позволяет материалу C наследия использовать другую арену памяти, а затем нормальное распределение памяти на С++.
Эта вторая маркерная точка интересна в свете того, что вы в конце концов предлагаете, отбрасывая tcmalloc
, чтобы заменить функции наследия C и использовать С++.
В стандарте есть сноска, которая объясняет, почему они не используют let malloc()
call ::operator new()
:
Цель состоит в том, чтобы реализовать оператор new(), вызвав std:: malloc() или std:: calloc(). Другими словами, они хотят избежать циклической зависимости.
Однако, хотя он позволяет operator new()
вызывать malloc()
, я не уверен, что стандарт действительно требует его. Таким образом, чтобы быть в безопасности, вы, вероятно, захотите ввести tcmalloc
в области C и С++.
Вы указали, что уже знаете, как это сделать для C. Для С++ это можно сделать, просто предоставив весь набор глобальных operator new()/delete()
функций в вашем коде, подходящим образом написанный для вызова tcmalloc
под обложками, Стандарт С++ указывается в 3.7.4 Dynamic storage duration
:
Библиотека предоставляет определения по умолчанию для глобальных функций распределения и освобождения. Некоторые функции глобального распределения и освобождения заменяются.
Программа на С++ должна предоставлять не более одного определения сменной функции распределения или освобождения. Любое такое определение функции заменяет версии по умолчанию, предоставленной в библиотеке.
Следующие функции распределения и освобождения неявно объявляются в глобальной области видимости каждой единицы перевода программы:
-
void* operator new(std::size_t);
-
void* operator new[](std::size_t);
-
void operator delete(void*);
-
void operator delete[](void*);
Ответ 2
Ok. Выкопал старый рабочий проект стандарта (2/28/2011 rev 3242.) Кажется, что соответствующие разделы 3.7.4 Dynamic storage duration
и 18.6.1 Storage allocation and deallocation
.
Короче говоря, кажется, что пангалактический крюк, который мне нужен, - это глобальные новые и удаленные операторы. Если кто-то соблюдает некоторую семантику (в 3.7.4.1
и 3.7.4.2
: в основном делегировать new_handler
по мере необходимости), то можно заменить
void* operator new(std::size_t);
void* operator new[](std::size_t);
void operator delete(void*);
void operator delete[](void*);
чтобы остановить управление памятью по умолчанию для всей программы на С++. Я до сих пор не могу найти раздел, который доказывает @paxdiablo правильно, но я готов работать с ним на данный момент.
Ответ 3
Вам не нужно беспокоиться о какой-либо памяти, выделенной сгенерированным кодом Flex или Bison; они сами позаботятся об этом. Для остальных просто используйте new
и delete
. (И Flex и Bison способны генерировать С++. В общем, я нашел запуск вывода через простой script, использование sed
было достаточным, чтобы сделать их вывод действительно совместимым с С++.)