Изменения сигнатуры функции для интерфейсов, совместимых с прямой связью

#c #macros #libraries

#c #макросы #библиотеки

Вопрос:

Существует ли обычная практика расширения C API при сохранении прямой совместимости?

Предположим, я хочу предоставить функцию foo в библиотеке c:

 foo(int value);
  

Теперь в более поздней версии я хотел бы расширить foo, чтобы разрешить другой параметр.

 foo(int value, const char *description_may_be_NULL);
  

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

Было бы разумно предоставить макрос, такой, что:

 #define MYLIB_API 2
#include <mylib.h>
/* the header will #define foo foo2 */
  

Это позволило бы избежать использования неприятных имен на практике.

Существуют ли какие-либо общие методы обработки конфликтующей прямой совместимости и элегантного кода для будущих версий? Будут оценены любые примеры того, как популярные API C справлялись с этим в прошлом.

Редактировать: К сожалению, я четко не упомянул об этом: foo(int) подпись уже установлена, поэтому введение параметров varargs / struct также было бы несовместимым изменением API. На самом деле маловероятно, что многие функции изменятся со временем, но некоторые в конечном итоге изменятся. Оплата вперед за все функции с необычной подписью кажется мне высокой ценой.

Комментарии:

1. Ядро UNIX и Linux (фактически интерфейс библиотеки C), как правило, делает именно то, что вы предлагаете, предоставляя новую обновленную функцию как foo2 . Иногда используются макроигры, чтобы «прозрачно» обеспечить поддержку больших файлов. Я должен просто принять и задокументировать новую функцию, если только вы не в состоянии глобально обновить все вызовы функций для вашей библиотечной функции и все файлы заголовков. Играя в макро-игры, так что это foo() действительно foo2() может привести к путанице в один прекрасный день, когда .h файл будет использоваться не так, как вы ожидаете.

2. open (2), был расширен, чтобы использовать файловый режим версии 6, в UNIX только что были созданы creat(2) и open (2) только для существующих файлов. Флаг O_CREAT был добавлен к вызову совместимым способом, поэтому иногда с помощью флагов вы можете обойтись без расширений. В этом докладе отлично обсуждаются проблемы, возникающие в результате ускоренного проектирования ABI и необходимости избегать looseness использования расширяемого интерфейса для сохранения возможности внесения совместимых изменений — см. youtube.com/watch?v=vlozCt8WfnU

Ответ №1:

Передайте указатель на структуру…

 typedef struct
{
    int value;
} typeStructure;

void foo(const typeStructure * const pTypeStructure);
  

При typeStructure расширении более старые версии foo будут просто игнорировать новые поля.

Комментарии:

1. Включите версию в эту структуру, и это, безусловно, самый разумный вариант. Теперь существует четко определенная сигнатура функции. Версия сообщает вам, какие поля действительно существуют (т. Е.: не удаляйте ссылки на мусорные поля из значений unset struct!). И даже там, где поля существуют в нескольких версиях, номер версии сообщает вам, что должны означать эти поля.

2. Я думаю, что это самое чистое решение. Однако это усложняет сигнатуры функций и вызовы, которые могут быть расширены, а могут и не быть расширены в будущем. Это неприемлемое значение по умолчанию для библиотеки, если у вас нет особых серьезных ожиданий от будущих расширений. Это доказывает, что в C нет хорошего решения для этой проблемы.

3. @Zulan However it does complicate function signatures and calls that may or may not be extended in the future. Если вы имеете в виду, что подпись foo не может измениться, чтобы оставаться обратно совместимой, вы абсолютно правы.

4. @FiddlingBits я понимаю, что подпись не нужно изменять, и обратная совместимость может быть реализована с использованием некоторого флага версии или зарезервированного заполнения в структуре. Я имел в виду, что если у меня будет 50 вызовов API и, вероятно, один или два изменятся в будущем, то я бы не стал разрабатывать все 50 сигнатур так, чтобы они принимали структуру вместо обычных параметров.

5. @Zulan Звучит как интересный последующий вопрос.

Ответ №2:

Вы могли бы создать переменную функцию:

 void foo(int arg, ...);
  

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

Ответ №3:

Используйте foo(); . отсутствие аргумента внутри круглых скобок означает неопределенное количество аргументов, и вы можете вызвать его с любым количеством параметров.

Другим способом является функция с va_args переменным аргументом, подобная printf() , или переменный макрос, который вызовет ваш foo

#define FOO(...) foo(__VA_ARGS_)

Комментарии:

1. Следуя этому предложению, вы должны указать на недостатки и отсутствие проверки ошибок.