Какие недостатки сводят вас с ума в C API (включая стандартные библиотеки, сторонние библиотеки и заголовки внутри проекта)? Цель состоит в том, чтобы выявить ошибки проектирования API в C, чтобы люди, пишущие новые библиотеки C, могли учиться на ошибках прошлого.
Объясните, почему недостаток плох (желательно с примером), и попробуйте предложить улучшение. Хотя ваше решение может быть непрактичным в реальной жизни (его слишком поздно исправлять strncpy
), оно должно дать фору будущим авторам библиотек.
Хотя основное внимание в этом вопросе уделяется API C, проблемы, влияющие на вашу способность использовать их на других языках, приветствуются.
Пожалуйста, дайте один недостаток за ответ, чтобы демократия могла отсортировать ответы.
c
api-design
pitfalls
Джои Адамс
источник
источник
malloc
ее исправит строка. Я думаю, что хороший пример с первым ответом может помочь этому вопросу процветать. Спасибо!Ответы:
Функции с противоречивыми или нелогичными возвращаемыми значениями. Два хороших примера:
1) Некоторые функции Windows, которые возвращают HANDLE, используют NULL / 0 для ошибки (CreateThread), некоторые используют INVALID_HANDLE_VALUE / -1 для ошибки (CreateFile).
2) Функция POSIX 'time' возвращает '(time_t) -1' в случае ошибки, что на самом деле нелогично, поскольку time_t может иметь тип со знаком или без знака.
источник
int time(time_t *out);
иBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
.Функции или параметры с неописательными или утвердительно запутывающими именами. Например:
1) CreateFile в Windows API фактически не создает файл, он создает дескриптор файла. Он может создать файл, так же как и «open», если его попросить через параметр. Этот параметр имеет значения с именами «CREATE_ALWAYS» и «CREATE_NEW», имена которых даже не намекают на их семантику. (Означает ли 'CREATE_ALWAYS', что он потерпит неудачу, если файл существует? Или он создает новый файл поверх него? Означает ли «CREATE_NEW», что он всегда создает новый файл и дает сбой, если файл уже существует? Или он создает новый файл? файл поверх него?)
2) pthread_cond_wait в POSIX pthreads API, который, несмотря на свое имя, является безусловным ожиданием.
источник
pthread_cond_wait
не означает , что «условно ждать». Это относится к тому факту, что вы ожидаете переменную условия .Непрозрачные типы, которые передаются через интерфейс как дескрипторы удаленных типов. Проблема, конечно, в том, что компилятор не может проверить код пользователя на правильность типов аргументов.
Это входит в различные формы и ароматы, включая, но не ограничиваясь:
void*
злоупотреблениеиспользование
int
в качестве дескриптора ресурса (пример: библиотека CDI)строковые аргументы
Чем более различные типы (= нельзя использовать полностью взаимозаменяемо) сопоставляются с удаленным типом того же типа, тем хуже. Конечно, решение состоит в том, чтобы просто обеспечить безопасные непрозрачные указатели по типу (пример C):
источник
Функции с непоследовательными и часто громоздкими соглашениями о возврате строк.
Например, getcwd запрашивает предоставленный пользователем буфер и его размер. Это означает, что приложение должно либо установить произвольное ограничение на текущую длину каталога, либо сделать что-то вроде этого ( из CCAN ):
Мое решение: вернуть
malloc
строку ed. Это просто, надежно и не менее эффективно. За исключением встроенных платформ и старых систем,malloc
на самом деле это довольно быстро.источник
snprintf(buf, 32, "%d", n)
, когда длина вывода предсказуема (конечно, не более 30, если толькоint
она не очень велика в вашей системе). Действительно, malloc недоступен во многих системах, но для настольных и серверных сред он есть, и он работает очень хорошо.Функции, которые принимают / возвращают составные типы данных по значению или используют обратные вызовы.
Еще хуже, если указанный тип является объединением или содержит битовые поля.
С точки зрения вызывающего абонента C, это действительно нормально, но я не пишу на C или C ++, если это не требуется, поэтому я обычно звоню через FFI. Большинство FFI не поддерживают объединения или битовые поля, а некоторые (например, Haskell и MLton) не могут поддерживать структуры, передаваемые по значению. Для тех, кто может обрабатывать структуры по значению, по крайней мере Common Lisp и LuaJIT вынуждены идти по медленным путям - Интерфейс Common Foreign Function Lisp должен делать медленный вызов через libffi, а LuaJIT отказывается JIT-компилировать путь кода, содержащий вызов. Функции, которые могут вызывать обратные вызовы хостов, также запускают медленные пути в LuaJIT, Java и Haskell, поскольку LuaJIT не может скомпилировать такой вызов.
источник