Каков "правильный" способ согласования malloc и new в смешанной программе C/С++? - программирование
Подтвердить что ты не робот

Каков "правильный" способ согласования 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. Пожалуйста, укажите имена тех разделов стандарта С++, которые поддерживают ваш ответ.

4b9b3361

Ответ 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 было достаточным, чтобы сделать их вывод действительно совместимым с С++.)