Проблема — перегрузка оператора new и удаление в рамках с -02 или выше оптимизации

мы сталкиваемся с проблемой с libc ++, которая идет в комплекте с Xcode 9.2

сценарий:

У нас есть фреймворк, который перегружает операторы new и delete.
Определение этих операторов new и delete скрыто внутри dll с указанием яблока, определенным здесь: https://developer.apple.com/library/content/technotes/tn2185/_index.html#//apple_ref/doc/uid/DTS10004200-CH1-SECTION13

Также у нас есть приложение, которое связывается с этой платформой и перегружает собственный оператор new и delete.

проблема:

Теперь проблема в том, что если внутри фреймворка создается строка (std :: string), она вызывает новый оператор на стороне приложения для выделения памяти, но во время уничтожения она вызывает
оператор на стороне каркаса удалить. Это может привести к повреждению памяти из-за различной реализации кучи на стороне приложения и на платформе, и это определенно является проблемой.

Эта проблема наблюдается только при сборке релиза фреймворка, когда используется уровень оптимизации -o2 или выше. Если мы передали компилятору флаг -fno-inline, эта проблема не наблюдается. Также эта проблема не наблюдается с xcode 8.2.

После дальнейшего изучения этой проблемы я обнаружил, что деструктор basic_string встроен в текущей версии libc ++, что не относится к libc ++, который поставляется вместе с xcode 8.2.
На clang forum есть обсуждение этого вопроса: https://reviews.llvm.org/D24599

Я предполагаю, что из-за встраивания во время связывания во время выполнения деструктор basic_string ссылается на оператор на стороне фреймворка, чтобы освободить выделенную память, но мне нужно подтверждение моей теории.

Если это так, то должны ли мы строить нашу платформу с флагом -fno-inline? Есть ли какое-то существенное снижение производительности, если мы используем этот флаг или есть какой-то другой подход, который мы должны рассмотреть?

Определение перегруженного оператора new и delete:

void * оператор new (size_t len) throw (std :: bad_alloc) {…}

void * оператор new (std :: size_t len, const std :: nothrow_t & _nothrow) throw () {…}

void * оператор new [] (size_t len) throw (std :: bad_alloc) {…}

оператор void delete (void * ptr) throw () {…}

оператор void delete (void * ptr, const std :: nothrow_t & _nothrow) throw () {…}

оператор void delete [] (void * ptr) throw () {…}

ищу помощь

Я предоставлю больше информации, если это необходимо

0

Решение

Из технической записки:

Ваши замены будут в действительности во всем приложении. Даже код в других единицах связи (разделяемые библиотеки) будет вызывать ваш пользовательский новый и удалять.
Во всем приложении (все блоки связывания) должно быть только одно определение для замененного нового и удаления. Это гарантирует, что если владение памятью будет перенесено через границу разделяемой библиотеки, оно будет правильно удалено.

У вас есть два определения нового / удаления в вашем приложении. Это уже плохо (тм).

В общем случае разделяемые библиотеки не должны переопределять эти операторы, если только это не единственная работа разделяемой библиотеки. В противном случае становится вероятным, что приложение будет ссылаться на более чем одно определение переопределенного new / delete. В редких случаях разделяемой библиотеке может быть удобно иметь частные определения этих операторов. Это делается путем связывания с флагом имени -unexported_symbols_list и размещения следующих символов в файле неэкспорта.

Можете ли вы перепроверить, действительно ли библиотека следовала этому правилу — т.е. не экспортировала символы?

При этом автор должен убедиться, что владение памятью не передается в или из этой общей библиотеки. Обратите внимание, что передача владения памятью может происходить незаметными способами, такими как передача объектов с подсчетом ссылок (например, std :: string), генерирование исключений, которые содержат сообщение, выделенное для кучи (например, std :: runtime_error) или имеющий встроенный конструктор распределения ресурсов, причем соответствующий деструктор не встроенный (или наоборот).

Я бы посоветовал не использовать флаг -fno-inline в качестве «быстрого исправления», так как это может только скрыть текущую очевидную проблему (множественные новые / удалить определения), но в то же время допустить повреждение памяти в дальнейшем. Если у вас нет способа избавиться от повторяющихся определений (что было бы гораздо лучшим выбором для IMO), вы можете только попытаться убедиться, что заголовки никогда не содержат ничего, что выделяет память при возможности быть встроенным — но даже то, что я ‘ буду расценивать как оба трудных & рискованно.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]