Мне бы хотелось разобраться в вопросе (не)совместимости между различными компиляторами
C/C++. Часто оказывается, что между собой несовместимы даже разные версии одного и
того же компилятора. Доступной и детальной информации по этой теме мне найти не удалось.
Вопросы такие:
1) Я знаю, что с совместимостью компиляторов C++ все очень плохо, потому что язык
слишком переусложнен, имеет бесконечное количество тонкостей, исключений из правил
и пунктов, которые определяются реализацией. Поэтому я не очень понимаю, зачем используют
всякие export "C" и extern "C", если двоичные файлы (например, lib) практически всегда
оказываются совместимы лишь с тем компилятором, которым они сделаны. Так в чем смысл?
2) Если разработка lib, dll и a файлов ведется с использованием языка C, то как в
этом случае обстоят дела с совместимостью? Все так же плохо, как если бы разработка
велась на C++? Или нет?
3) Есть ли способы делать двоичные библиотеки максимально совместимыми? Чтобы написанная
однажды библиотека dll могла быть подключена в самых разных языках без боли и страданий?
Ответы
Ответ 1
Несовместимость бинарных модулей (далее, для краткости, просто "модулей"), произведенных
разными компиляторами, определяется в основном следующими тремя аспектами:
Разные правила декорирования имен экспортируемых символов
Разное устройство объектов стандартной библиотеки
Разные правила расположения полей структур в памяти
Первый пункт характерен фактически только для С++: в Си существует набор характерных
для конкретной аппаратной платформы соглашений о вызове (например, stdcall, fastcall
и cdecl для x86), которые довольно четко прописывают правила декорирования имен. Второй
пункт относится и к Си и к С++, но в Си не очень много "объектов стандартной библиотеки"
- в голову приходит только FILE*, и экспортировать его через границы модулей нет никакого
смысла.
Таким образом да, действительно можно сказать, что С++ "хуже" чем Си в плане бинарной
совместимости. Это разумеется не значит, что не нужно на нем писать, это лишь значит,
что на границе модулей нужно использовать интерфейс в стиле Си (либо использовать стандартизированный
объектно-ориентированный интерфейс, например Component Object Model в Windows).
Есть ли способы делать двоичные библиотеки максимально совместимыми? Чтобы написанная
однажды библиотека dll могла быть подключена в самых разных языках без боли и страданий?
Использование DLL на С/С++ в других языках это больше чем вопрос бинарного интерфейса
(например, в них может просто не быть концепции заголовочных файлов, указателей и т.п.),
но обычно да, библиотека с интерфейсом в стиле Си может быть использована и из других
языков с тем или иным количеством дополнительных телодвижений.
Рекомендации для обеспечения максимальной бинарной совместимости:
Экспортируйте через границы бинарного модуля только простые функции с припиской extern
"C" (т.е, никаких классов, шаблонов, перегруженных функций, пространств имен и т.п.)
Передавайте через границы модулей только простые типы, указатели на них и указатели
на функции.
Если все же передаете структуры, сделайте первым членом структуры ее размер. Это
позволит, если вы натолкнетесь на различия по выравниванию полей, обнаружить несоответствие
в общем размере структуры и хотя бы нормально вернуть ошибку.
Не передавайте через границы модулей объекты стандартной библиотеки, например указатели
FILE*.
Блоки динамической памяти должны освобождаться всегда в том же модуле, в котором
были выделены. Т.е., если библиотека возвращает программе-клиенту указатель на блок
памяти, выделенный malloc внутри себя, она должна предоставлять специальную функцию
для его освобождения (вызывающую внутри себя free), вместо того, чтобы полагаться на
вызов free в программе-клиенте.
Ответ 2
Толстый троллинг по поводу "недостатков" языка С++ продолжается. Ну что же, ринемся
на защиту детища Страуструпа.
1) Я знаю, что с совместимостью компиляторов C++ все очень плохо,
С совместимостью компиляторов С++ все очень хорошо, гораздо лучше, чем с совместимостью
компиляторов с каких-либо других языков. Вплоть до того, что многие языки имеют компилятор
только под одну платформу и/или от одного производителя. Конечно, такой подход улучшает
совместимость, но ухудшает переносимость и тормозит развитие. При отказе единственного
производителя компилятора от поддержки языка 100500 разработчиков повисают в воздухе.
Достаточно вспомнить отказ даже такого гиганта как Микрософт от обратной совместимости
в языке Visual Basic при переходе от версии VB6 к версии VB.net.
потому что язык слишком переусложнен,
имеет бесконечное количество тонкостей, исключений из правил и
пунктов, которые определяются реализацией.
Язык С++ не переусложнен. Просто в языке С++ не включена "защита от дурака". В награду
за не включенную "защиту от дурака" пользователи имеют возможность делать то, что им
нужно, а не то, что позволяет им язык. Чтобы пользоваться всеми возможностями языка
С++ недостаточно выучить синтаксис С++ по первому изданию книжки Страуструпа. Надо
еще читать книжки, которые выходят по мере развития языка и в которых обсуждаются как
раз те новые возможности, которые некоторых разработчиков приводят к созданию крашащегося
кода, а другим разработчикам позволяют съэкономить 100500 часов рабочего времени. Внесение
в язык С++ новых возможностей (например шаблонов) порождает взаимодействие этих новых
возможностей со старыми возможностями (например с классами). Это взаимодействие может
служить как источником ошибок, так и источником новых идей и подходов. Многие достойные
люди занимаются исследованиями в этих направлениях и публикуют свои результаты исследований.
Изучая эти исследования можно избежать ошибок и воспользоваться новыми подходами. Еще
раз повторю что в языке С++ не включена "защита от дурака". И поэтому, чтобы не делать
ошибок в языке С++, надо точно понимать, что именно ты делаешь.
Поэтому я не очень понимаю, зачем используют всякие export "C" и
extern "C",
Как тут правильно заметили это отключает манглинг имен.
если двоичные файлы (например, lib) практически всегда
оказываются совместимы лишь с тем компилятором, которым они сделаны.
Так в чем смысл?
Под совместимостью различных компиляторов языка С++ имеется ввиду совместимость на
уровне исходного текста. Двоичную совместимость объектников, бинарников и библиотек
lib никто и никогда не гарантировал. Кстати, это относится и к другим языкам (за исключением
языков с виртуальными машинами, но там за это платят тем, что приходится за собой таскать
все тупиковые решения и ошибки ради обратной совместимости).
2) Если разработка lib, dll и a файлов ведется с использованием языка
C, то как в этом случае обстоят дела с совместимостью? Все так же
плохо, как если бы разработка велась на C++? Или нет?
Кстати, никто не гарантировал совместимость объектников, бинарников и библиотек lib
для языка Си. Трансляторы с языка Си от разных производителей производят объектники
разного формата. Ну и что в этом такого?
3) Есть ли способы делать двоичные библиотеки максимально
совместимыми? Чтобы написанная однажды библиотека dll могла быть
подключена в самых разных языках без боли и страданий?
Если в системе Windows dll имеет "pure Си" интерфейс, то ее можно вызвать из многих
других языков. Количество боли и страданий при этом зависит от разработчика интерфейса
между dll и этим самым языком, из которого dll вызывается. Собственно языки С/С++ к
этому не имеют никакого отношения.
UPD1:
Я не начинал толстый троллинг по поводу недостатков C++, просто это
мое мнение, которое основывается на определенном опыте работы с этим
языком, в том числе в международных группах разработчиков. – Максим 2
минуты назад
Уважаемый Максим, количество использующих язык С++ разработчиков в мире (в том числе
в международных группах разработчиков) убедительно показывает, что язык C++ вполне
жизнеспособен. Если бы это было не так, то от языка С++ давно бы отказались, как отказались
в свое время от 100500 других языков.
UPD2:
С тем, что С++ жизнеспособен, никто не спорит. Но все-таки в нем
действительно много разных тонкостей и хитростей. – HolyBlackCat 10
секунд назад
Как я уже сказал, все эти "тонкости и хитрости" это следствие того, что новые возможности
добавляются в язык и на уровне транслятора отсутствуют семантические и синтаксические
ограничения на использование этих новых возможностей. Также на уровне транслятора
отсутствуют семантические и синтаксические ограничения на взаимодействие новых возможностей
со старыми возможностями. Это, с одной стороны, добавляет мощи. А с другой стороны,
ограничения на использование новых средств должны храниться в голове программиста,
а не прошиваются в синтаксисе языка.
В любом случае, все, кому С++ кажется слишком сложным/непонятным/переусложненным/плохо
совместимым может пользоваться любыми другими языками вместо того, чтобы перечислять
мнимые недостатки С++. Потому что все эти якобы недостатки С++ это вовсе не недостатки,
а это такой подход при котором транслятор не мешает Вам выстрелить себе в ногу, но
зато дает возможность выстрелить на 100500 километров при правильном использовании.
На свете есть 100500 трансляторов, которые мешают программисту выстрелить себе в ногу,
но зато и ограничивают возможности.
UPD3:
Извините за частое употребление идиомы 100500, но уж очень она тут подходит. :-)