Хрупкая проблема двоичного интерфейса
Эта статья нуждается в дополнительных цитатах для проверки . ( май 2009 г. ) |
Проблема хрупкого двоичного интерфейса или FBI — это недостаток некоторых объектно-ориентированных языков компиляторов программирования , в которых внутренние изменения в базовой библиотеке классов могут привести к тому, что библиотеки-потомки или программы перестанут работать. Это пример хрупкости программного обеспечения .
Эту проблему чаще называют хрупкого базового класса проблемой или FBC ; однако этот термин имеет более широкий смысл.
Причина
[ редактировать ]Проблема возникает из-за «ярлыка», используемого в компиляторах для многих распространенных объектно-ориентированных (ОО) языков, конструктивной особенности, которая сохранялась, когда объектно-ориентированные языки развивались из более ранних языков структурированного программирования, не являющихся объектно-ориентированными (ОО) , таких как C и Pascal .
В этих языках не было объектов в современном понимании, но существовала подобная конструкция, известная как запись (или «структура» в C), которая хранила различную связанную информацию в одном фрагменте памяти. Доступ к частям в конкретной записи осуществлялся путем отслеживания начального местоположения записи и знания смещения от этой начальной точки до рассматриваемой части. Например, запись «человека» может содержать имя, фамилию и отчество, чтобы получить доступ к инициалу, который пишет программист. thisPerson.middleInitial
который компилятор превращает во что-то вроде a = location(thisPerson) + offset(middleInitial)
. Современные процессоры обычно содержат инструкции для этого распространенного вида доступа.
Когда впервые разрабатывались компиляторы объектно-ориентированного языка, использовалась большая часть существующей технологии компилятора, а объекты строились на основе концепции записи. В этих языках объекты ссылались на их начальную точку, а доступ к их общедоступным данным, известным как «поля», осуществлялся через известное смещение. По сути, единственным изменением было добавление к записи еще одного поля, которое указывает на неизменяемую таблицу виртуальных методов для каждого класса, так что запись описывает как его данные, так и методы (функции). При компиляции смещения используются для доступа как к данным, так и к коду (через таблицу виртуальных методов).
Симптомы
[ редактировать ]Это приводит к проблемам в более крупных программах , построенных на основе библиотек . Если автор библиотеки изменит размер или расположение общедоступных полей внутри объекта, смещения станут недействительными и программа перестанет работать. Это проблема ФБР.
Хотя можно ожидать, что изменения в реализации вызовут проблемы, коварная особенность FBI заключается в том, что на самом деле ничего не изменилось, а только макет объекта, который скрыт в скомпилированной библиотеке. Можно было бы ожидать, что если кто-то изменит doSomething
к doSomethingElse
что это может вызвать проблемы, но в этом случае можно создавать проблемы, не меняя doSomething
, это можно вызвать так же легко, как перемещение строк исходного кода для ясности. Хуже того, программист практически не имеет контроля над конечным макетом, созданным компилятором, что делает эту проблему почти полностью скрытой от глаз.
В сложных объектно-ориентированных программах или библиотеках классы самого высокого уровня могут наследовать от десятков классов. Каждый из этих базовых классов также может быть унаследован сотнями других классов. Эти базовые классы хрупкие, поскольку небольшое изменение в одном из них может вызвать проблемы для любого класса, который наследуется от него, либо напрямую, либо от другого класса, который это наследует. Это может привести к тому, что библиотека рухнет, как карточный домик , поскольку одно изменение базового класса повредит многие классы. Проблему можно не заметить при написании модификаций, если дерево наследования сложное. Действительно, разработчик, модифицирующий базовый класс, обычно не знает, какие классы, разработанные другими, его используют.
Решения
[ редактировать ]Языки
[ редактировать ]Одним из решений хрупкой проблемы бинарного интерфейса является написание языка, который знает о существовании проблемы и не позволяет ей случиться. Большинство специально написанных объектно-ориентированных языков, в отличие от тех, которые произошли от более ранних языков, создают все свои таблицы смещений во время загрузки. В этот момент будут «замечены» изменения в макете библиотеки. Другие объектно-ориентированные языки, такие как Self , создают все во время выполнения, копируя и изменяя объекты, найденные в библиотеках, и поэтому на самом деле не имеют базового класса, который может быть хрупким. Некоторые языки, такие как Java , имеют обширную документацию о том, какие изменения можно безопасно вносить, не вызывая проблем со стороны ФБР.
Другое решение — записать промежуточный файл со списком смещений и другой информации со стадии компиляции, известный как метаданные. Затем компоновщик использует эту информацию для исправления ошибок при загрузке библиотеки в приложение. Такие платформы, как .NET, делают это.
Однако рынок выбрал языки программирования, такие как C++ , которые действительно «зависят от позиции» и, следовательно, демонстрируют FBI. В этих случаях еще есть ряд решений проблемы. Один из них возлагает бремя на автора библиотеки, заставляя его вставлять несколько объектов-заполнителей на случай, если им понадобится добавить дополнительную функциональность в будущем (это можно увидеть в структурах, используемых в библиотеке DirectX ). Это решение работает хорошо до тех пор, пока у вас не закончатся эти манекены — и вы не хотите добавлять слишком много, потому что это занимает память.
Objective-C 2.0 предоставляет устойчивые переменные экземпляра , имея дополнительный уровень косвенности для доступа к переменным экземпляра.
Другое частичное решение — использовать шаблон Bridge , иногда известный как « Pimpl » («Указатель на реализацию»). Платформа Qt является примером такой реализации. Каждый класс определяет только один элемент данных, который является указателем на структуру, содержащую данные реализации. Размер самого указателя вряд ли изменится (для данной платформы), поэтому изменение данных реализации не влияет на размер публичной структуры. Однако это не позволяет избежать других критических изменений, таких как введение виртуальных методов в класс, у которого их нет, или изменение графа наследования.
Линкеры
[ редактировать ]Другое решение требует более умного компоновщика. В исходной версии Objective-C формат библиотеки позволял использовать несколько версий одной библиотеки и включал некоторые функции для выбора нужной библиотеки при вызове. Однако это было необходимо не всегда, поскольку смещения были необходимы только для полей, поскольку смещения методов собирались во время выполнения и не могли вызвать FBI. Поскольку методы имеют тенденцию меняться чаще, чем поля, у ObjC изначально было мало проблем ФБР, а те, которые были, можно было исправить с помощью системы управления версиями. В Objective-C 2.0 добавлена «современная среда выполнения», которая также решила проблему ФБР для полей. Кроме того, язык TOM использует смещения, собираемые во время выполнения, для всего, что делает FBI невозможным.
Использование статических библиотек вместо динамических, где это возможно, является еще одним решением, поскольку в этом случае библиотеку нельзя будет изменить без перекомпиляции приложения и обновления используемых им смещений. Однако у статических библиотек есть свои серьезные проблемы, такие как больший размер двоичного файла и невозможность использовать новые версии библиотеки «автоматически» по мере их появления.
Архитектура
[ редактировать ]В этих языках проблема уменьшается за счет обеспечения единого наследования (поскольку это снижает сложность дерева наследования) и за счет использования интерфейсов вместо базовых классов с виртуальными функциями , поскольку сами интерфейсы не содержат кода, а только гарантия того, что каждый сигнатура метода, которую объявляет интерфейс, будет поддерживаться каждым объектом, реализующим интерфейс.
Метод распространения
[ редактировать ]Вся проблема разваливается, если доступен исходный код библиотек. Тогда простая перекомпиляция поможет.