Евгений Неручек @reficul0
Разработка по ту сторону кроличьей норы
Информация
- В рейтинге
- Не участвует
- Откуда
- München, Bayern, Германия
- Зарегистрирован
- Активность
Специализация
Software Developer, Application Developer
C#
C++
Multiple thread
C++ Boost
Git
High-loaded systems
Rust
Кажется, что нам не всегда нужна синхронизация. Синтетический пример:
Да, я и правда не уточнил, что речь шла про вставку в конец. Делать вставку не в конец - очевидно гиблое дело. Исправил, спасибо.
Если говорить коротко, то, да, рано или поздно управляющий поток перейдёт границу, чтобы обратиться к имплементации STL из libc++ (clang), libstdc++(gcc), либо же имплементации от msvc.
Вопрос ABI совместимости в STL был очень хорошо разобран в статье Binary Banshees and Digital Demons с перпективы автора многих proposal-ов в стандарт. К сожалению, я не видел версии этой статьи на русском, лично мне она кажется стоящей перевода.
Вольно переводя материал этой статьи и немного утрируя, можно сказать следующее:
Проблемы с менеджментом памяти и конструированием/деструкцией контейнеров можно решить при помощи
std::polymorphic_allocator
, с егоstd::memory_resource
интерфейсом, который гарантирует ABI совместимость между разными версиями STL. Это значит, что если в вашей программе вы используете header из новой версии STL с более старым бинарём самой стандартной бибилиотеки (libc++ / libstdc++), то у вас гарантированно должно всё работать.Но решение с
std::memory_resource
проблемно, и суть проблема та же, что и у Си API. Дизайн решения - устаревший, и лучше уже не станет:Дизайн
std::memory_resource
был зарелижен уже устаревшим, вдохновлённымstd::moneypunct
(дизайном локали из C++98-эры!).Лучше этот интерфейс уже точно не станет, потому что он связан с двух сторон: с одной стороны - требование полной ABI-совместимости, а с другой - его реализация на виртуальных функциях.
В резульате, улучшения стандартного аллокатора в новых версиях стандарта просто неприменимы для
std::polymorphic_allocator
. Когда вstd::allocator
добавляютallocate_at_least
, то же самое уже не добавят вstd::polymorphic_allocator
, потому добавление новой фунции сломает ABI совместимость.Здравствуйте! Наверное, вы имели ввиду, что нужно использовать Си API.
Я согласен, что если нужна гарантированная бинарная совместимость без лишних заморочек, то поддержка Си API может того стоить, и, к сожалению, это решение безальтернативное. Но, вцелом, как и у остальных решений, у подхода есть свои плюсы и минусы.
На самом деле есть ещё более простой совет - лучше не приводить к нарушению бинарной совместимости. Проще использовать header-only версию библиотеки, либо же пересобрать её под целевой тулсет, либо же найти уже пересобранную.
Я писал про это в выводе: "В одном чёрном-чёрном доме ..".
Из вывода
Насколько вы поняли, реальность ABI совместимости в C++ довольно сурова. Ещё более суровым её делает тот факт, что только при использовании Си API решение становится по-настоящему "бинарно-дружелюбным". Но, в то же время, со всем моим уважением к Си, код потеряет ту маленькую долю лакончиности и удобства, которое даёт нам С++, хотя, кажется, в тех редких случаях когда нам нужна 100% совместимость, другого выбора у нас нет.
Вы всегда можете написать Си API для своего юзкейса, перейти на использование POD типов, но на самом деле наилучший совет для перехода бинарной границы модулей с разным ABI — не переходить её. Чаще всего игра не стоит свеч
(и в очередной раз отстреленных ног), поэтому проще использоватьheader-only
версию библиотеки, если она есть. Или же пересобрать библиотеку под свой тулсет, или найти уже пересобранную.Я прошу от вас более объективных заявлений, следующих научной методологии, подтверждённых чем-то кроме вашего личного мнения, потому что ваши замечания неконструктивны и содержат логические ошибки.
Субьективное мнение, к сожалению, не поможет в улучшении статьи и дополнении её объективными фактами. В связи с чем ваша мотивация выглядит сомнительной.
Почему ваши замечания неконструктивны?
Основываясь на выбрке ваших высказываний (см. внизу), многие ваши замечания содержат вашу субьективную оценку. В связи с чем, ваши замечания - не конструктивны.
https://habr.com/ru/articles/710658/#comment_26055918
https://habr.com/ru/articles/710658/#comment_26055960
https://habr.com/ru/articles/710658/#comment_26078678
Почему ваши замечания содержат логические ошибки?
Вы допускаете логические ошибки следуя ненаучной методологии исследования, позволяя вашему субъективному мнению влиять на их результаты. Например, если взять ваш ответ:
При минимамльном приближении можно понять, что толковый словарь, какой бы именно вы не имели ввиду (прим. толковый словарь Даля, Ожигова), не определяет разницу между конечностью и фиксированностью памяти системного стека. Понятия конечности и фиксированности памяти системного стека может определять только спецификация языка/компилятора/системы, в рамках которой этот стек и определён.
Пардон, я опечатался:
Извините, я боюсь, что вы не прочитали этот пункт до конца.
Про то что стек не бесконечныйПро то, что количество динамической памяти - не бесконечно я рассказал в двух абзацах, следующих сразу за процитированным вами.Ещё раз аккуратно перечитал абзац из статьи и ваше замечание. Я понял что вы имеете ввиду, проблема в том, что когда я говорю про фиксированность размера стека в контексте переполнения буффера, я на самом деле имею ввиду фиксированность его максимально допустимого размера.
Я исправил формулировку на более однозначную, благодарю за помощь!
Извините, можете, пожалуйста, подтвердить свою точку зрения ссылкой на стандарт С++ (или спецификацию компилятора, вроде GCC/MSVC), где определена разница между понятиями конечности и фиксированности размера системного стека, и где приведена причинно-следственная связь этих понятий с переполнением стека из-за бесконечной рекурсии?
Насколько я знаю, это субективные понятия, которые, соответственно, могут интерпретироваться каждым по-разному.
Поэтому, дорогой коллега, прошу вас, пожалуйста, для подкрепления вашего субъективного мнения, привести ссылки на более объективные источники информации, вроде: стандарта С++, спецификации компиляторов, спецификации ABI (например Itanium), или же результатов исследований, следующих научной методологии.
Извините, но, субьективно, это высказывание мне не кажется конструктивным, особенно по отношению к новичкам.
Во-первых,
Чтобы его сделать его более объективным, вам нужно сопроводить своё высказывание градацией "важных внутренних механизмов языка С++", категоризированной по разным областям бизнеса, по которой будет видно важность каждого конкретного нюанса, и как знание о них влияет на бизнес. На вскидку, даже если бы я занялся такой категоризацией, субъективно, я бы не отнёс знание поднаготной ABI к мастхев знаниям для младшего разработчика. Кажется, что даже для множества разработчиков уровня middle и выше профит от этих знаний довольно эфимерный.
Во-вторых,
Я думаю, что, даже если это и возможно, выучить детали всех внутренних механизмов работы С++, то всё ещё невозможно постоянно держать их в голове и эффективно при этом работать. Потому что эти знания уходят корнями в бесчисленные нюансы работы ОС, о которых, в связи со сложностью устройства современных ОС, невозможно знать всё, заранее и сразу.
Поэтому,
Я думаю, что это хорошо для общего понимания - знать теорию, и как С++ работает "под капотом". Представление же о мире вида: "без знания всех этих нюансов у человека не получится написать ничего путного на С++", с моей точки зрения, не имеет ничего общего с реальностью.
Более того я считаю, что такой подход - деструктивен, он не ведёт ни к чему кроме разочаровния в бесконечной погоне за недостижимым "абсолютным знанием", в бесконечном цикле изучения "основных-основ" вкупе со штрудированем Александреску, The C Programming Language, и тонкостей языка Assembly.
Здравствуйте!
Подскажите, пожалуйста, как это отменяет исходное высказывание?
Размер стека всё ещё фиксирован, и бесконечное количество вызовов всё ещё приведёт к рекурсии.
Извините, я боюсь, что вы не прочитали этот пункт до конца. Про то что стек не бесконечный я рассказал в двух абзацах, следующих сразу за процитированным вами.
Да, вам нужно выбрать нужный язык в выпадающем списке:
Здравствуйте! Спасибо за статью!
Небольшой совет, для улучшения читаемости код лучше поместить в специальный блок:
Здравствуйте!
Я бы сказал, что мой личный опыт подтверждает теорию, которую я знаю. Действительно, сам механизм обработки исключений задекларирован в стандарте, но имплементация всё-ещё является платформо-зависимой, стандартного лайаута для С++ исключений, к сожалению не существует.
Но всё-ещё может вознакать проблема в разнице практического опыта, потому что доказать, что UB нет вцелом сложнее, чем доказать, что он есть. Мы можем видеть множество кейсов, в которых всё выглядит хорошо, но это не значит, что это не ведёт к UB.
Теперь, говоря о доказательной базе.
Например, есть спецификация Common Vendor ABI (Itanium C++ ABI), которую некоторые вендоры поддерживают в своих компиляторах.
Это попытка решить на уровне этой спецификации многие вопросы ABI совместимости, для которых стандарт оставил пространство для воображения.
По этому поводу даже был purposal WG21 N4028 Defining a Portable C++ ABI, но, судя по всему, он не был удовлетворён. В связи с чем, каждый компилятор горазд на свою имплементацию стандарта, в частности каждый сам решает хотят они поддерживать Common Vendor ABI (Itanium C++ ABI), или нет: GCC, к примеру, начиная с 3 версии, использует имплементацию Itanium:
Но жизнь в GCC есть и до его третьей версии, поэтому, даже при использовании только GCC, но разных его версий, мы можем увидеть странное поведение при переходе бинарной границы из-за разницы имплементаций.
Что конкретно может приводить к несовместимости?
Согласно Itanium Level II: C++ ABI 2.1 Introduction, стандарт С++ не настоял на следующих деталях:
Разница может появиться из-за разницы механизмов:
Раскрутки стека
Когда вылатает исключение происходит раскрутка стека, до тех пор, пока не будет встречен первый
catch
. Поскольку разные комиляторы могут использовать стек по разному, то огранизация стека может быть разной, что в свою очередь приведёт к тому, что во время обработки исключений два модуля, интерпретируя стек по разному, будут работать с его фреймами каждый по своим правилам.Вероятные последствия: нарушение целостности системного стека, UB с иногда стреляющим
SIGSEGV
/SIGBUS
.Создания и перехвата исключений
Механизм, используемый для создания и перехвата исключений (конструирвоание, копирование, перемещение, деструкция, выделение и освобождение памяти), может различаться в зависимости от компилятора.
Вероятные последствия: нарушение целостности памяти программы, UB с иногда стреляющим
SIGSEGV
/SIGBUS
.Лайаута исключений
Расположение объектов исключений в памяти (порядок полей, выравнивание, и т.д.) может различаться в зависимости от компилятора.
Вероятные последствия: UB с иногда стреляющим
SIGSEGV
/SIGBUS
.Менглинга имён
Поскольку ABI компиляторов может быть разным, они могут по-разному менглить и деменглить имена. У нас нет гарантий, наложенных С++ стандартом, что механизм менглинга имен будет одинаковым.
Вероятные последствия: в зависимости от имплементации, мы можем получить ODR violation и соответствующий UB.
Выглядит так, что у вас достаточно материала, чтобы написать статью на эту тему :)
Не понимаю как ваша реакция связана с объективной реальностью. Если вы готовы продолжать дискуссию, то прошу указать в чём я был не прав :)
Спасибо! Исправил.
Прошу прощения, я выразился не до конца ясно. Я думаю, что основная причина успеха вашего экспиримента скрывается в том, что вы используете одну и ту же платформу с одним и тем же компилятором.
При минимальном рассмотрении теория о том, что copy elision будет поддреживается в общем случае, представяется несостоятельной. Мои рассуждения, в результате которых я делаю такой вывод, следующие:
Во-первых,
Copy elision, как и любая другая опитимизация, описан в стандарте только своими эффектами (то что должно произойти с уровня абстракции языка), но стандарт не регламенитрует конкретную реализацию (то как имплементация конкретно должна это делать).
С этой точки зрения, два произвольно выбранных компилятора могут иметь разную реализацию. Да, количество путей, которыми можно сделать эту оптимизацию - строго ограничено, но в деталях она может отличаться. Эта разница в деталях могут сделать эту оптимизацию невозможной для двух разных модулей в общем случае. Ситуация получается аналогичной тому, как детали реализации механизма
vtable
, рассмотренного в статье, приводят к тому же эффекту.В частности,
Вызывающий функцию модуль, как и модуль, содержащий реализацию функции, всегда может быть собран со стандартом меньшим, чем С++17, например С++11/C++14. Поэтому компилятор, при сборке этого модуля, согласно стандарту С++11 и C++14, не обязан поддерживать copy elision.
Мой вывод:
Это implementation defined поведение, на которое я бы не стал завязываться, если вас интересует вопрос совместимости двух, потенциально собранных разными инструментами, модулей.
Поэтому, дорогой коллега @KanuTaH, если вы имеете хорошо проверенную информацию об обратном, или же вы готовы провести более подробное исследование, следующее более научной методологии, то я буду рад дополнить статью его результатами.
Извините, ваше утверждение основано на эксперименте с одним компилятором в одном конкретном окружении. Если вы претендуете на научный подход, то, вам нужна рандомизированная выборка, или ссылка на стандарт.
Вы всегда можете этим заняться, я буду рад дополнить статью результатами вашего исследования, коллега.
Прошу, пожалуйста, обратить внимание:
Очень интересный подход, спасибо, что поделились! И правда, если решать обработку ошибок на уровне системы, то проблем убудет. Это случайно не ОС под встроенную систему? От такого подхода с отказом от исключений на уровне системы немного веет программированием под встроенные системы.
В одной компании я увидел, как они реализовали свой механизм полиморфизма подтипов, в связи с чем они могли без зазрения совести делать вызовы "виртуальных" методов через бинарную границу. Но у этой компании и свой полноценный аналог STL был, со всякими дополнительными плюшками вида встроенной во фреймворк сериализации. Да и огромной она была, компания эта, деньги на разработку и необходимость в этих инструментах у них точно были.
Вцелом, я думаю, что такие подходы с переписыванием стандартных механизмов - очень дорогие, в связи с чем мало кто может себе такое позволить. И рассматривание таких механизмов - очень узкоспециализированная деятельность, далеко выходящая за рамки моего overview.