Підводні камені API в C [закрито]


10

Які деякі недоліки викликають гайки в API API (включаючи стандартні бібліотеки, сторонні бібліотеки та заголовки всередині проекту)? Мета полягає у визначенні підводних каменів API в C, щоб люди, що пишуть нові бібліотеки С, могли вчитися на помилках минулого.

Поясніть, чому вада погана (бажано на прикладі), і спробуйте запропонувати поліпшення. Хоча ваше рішення може не бути практичним у реальному житті (це вже пізно виправити strncpy), воно повинно дати голову майбутнім бібліотечним письменникам.

Хоча це питання приділяється API API, проблеми, які впливають на вашу здатність використовувати їх іншими мовами, вітаються.

Будь ласка, дайте один недолік за кожну відповідь, щоб демократія могла сортувати відповіді.


3
Джоуї, це запитання залежить від того, що воно не є конструктивним, просячи скласти список речей, які люди ненавидять. Тут є потенціал, щоб питання було корисним, якщо відповіді пояснюють, чому практики, на які вони вказують, погані та надають детальну інформацію про те, як їх вдосконалити. З цією метою, будь ласка, перенесіть свій приклад із запитання у власну відповідь та поясніть, чому це проблема / як mallocби вирішила її "d". Я думаю, що надання хорошого прикладу з першою відповіддю справді може допомогти процвітати в цьому питанні. Дякую!
Адам Лір

1
@Anna Lear: Дякую, що розповіла, чому моє питання було проблематичним. Я намагався підтримувати це конструктивно, просивши приклад та запропонував альтернативу. Я думаю, мені справді потрібні були приклади, щоб вказати на те, що я мав на увазі.
Джої Адамс

@Joey Adams Подивіться на це так. Ви задаєте питання, яке повинно "автоматично" вирішувати проблеми C API загальним чином. Там, де такі сайти, як StackOverflow, були розроблені таким чином, що на більш поширені проблеми з програмуванням легко знайти І відповісти на них. StackOverflow, природно, призведе до списку відповідей на ваше запитання, але більш структурованим способом, який легко шукати.
Ендрю Т Фіннелл

Я проголосував за закриття власного питання. Моєю метою було зібрати відповіді, які могли б послужити контрольним списком щодо нових бібліотек C. На сьогоднішній день у трьох відповідях використовуються слова типу "непослідовний", "нелогічний" або "заплутаний". Не можна об'єктивно визначити, чи порушує API будь-який із цих відповідей.
Joey Adams

Відповіді:


5

Функції з непослідовними або нелогічними значеннями повернення. Два хороших приклади:

1) Деякі функції Windows, які повертають HANDLE, використовують помилку NULL / 0 (CreateThread), деякі використовують INVALID_HANDLE_VALUE / -1 для помилки (CreateFile).

2) Функція POSIX 'time' повертається '(time_t) -1' при помилці, що дійсно нелогічно, оскільки 'time_t' може бути або підписаним, або непідписаним типом.


2
Власне, time_t підписується (як правило). Однак називати 31 грудня 1969 року "недійсним" є досить нелогічним. Я думаю, 60-ті були грубими :-) Якщо серйозно, рішенням було б повернути код помилки та передати результат через покажчик, як у: int time(time_t *out);та BOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);.
Joey Adams

Саме так. Це дивно, якщо time_t не підписано, і якщо time_t підписано, це робить один раз недійсним посеред океану дійсних.
Девід Шварц

4

Функції або параметри з не описовими або стверджувально заплутаними іменами. Наприклад:

1) CreateFile в API Windows фактично не створює файл, він створює обробку файлу. Він може створити файл так само, як "відкрити", якщо його попросять через параметр. Цей параметр має значення "CREATE_ALWAYS" та "CREATE_NEW", імена яких навіть не натякають на їх семантику. (Чи означає "CREATE_ALWAYS" невдачу, якщо файл існує? Або він створює новий файл поверх нього? Чи "CREATE_NEW" означає, що він створює новий файл завжди і виходить з ладу, якщо файл вже існує? Або він створює новий файл зверху?)

2) pthread_cond_wait в API pthreads POSIX, який, незважаючи на свою назву, - безумовне очікування.


1
Конд в pthread_cond_waitне означає , що «умовно чекати». Це стосується того, що ви чекаєте змінної умови .
Джонатан Райнхарт

Право, це безумовне очікування для стану.
Девід Шварц

3

Непрозорі типи, які передаються через інтерфейс у вигляді видалених ручок. Проблема, звичайно, полягає в тому, що компілятор не може перевірити код користувача на наявність правильних типів аргументів.

Це різноманітні форми та аромати, включаючи, але не обмежуючись ними:

  • void* зловживання

  • використання intв якості обробки ресурсів (приклад: бібліотека CDI)

  • строго набрані аргументи

Чим чіткіші типи (= неможливо використовувати повністю взаємозамінно) відображаються на видалений тип однотипного типу, тим гірше. Звичайно, засіб полягає в тому, щоб просто надати непрозорі вказівники на основі типу C (наприклад, C):

typedef struct Foo Foo;
typedef struct Bar Bar;

Foo* createFoo();
Bar* createBar();

int doSomething(Foo* foo);
void somethingElse(Foo* foo, Bar* bar);

void destroyFoo(Foo* foo);
void destroyBar(Bar* bar);

2

Функції з непослідовними і часто громіздкими умовами повернення рядків.

Наприклад, getcwd запитує буфер, що надається користувачем, та його розмір. Це означає, що програма або повинна встановити довільну межу поточної довжини каталогу, або зробити щось подібне ( від CCAN ):

 /* *This* is why people hate C. */
len = 32;
cwd = talloc_array(ctx, char, len);
while (!getcwd(cwd, len)) {
    if (errno != ERANGE) {
        talloc_free(cwd);
        return NULL;
    }
    cwd = talloc_realloc(ctx, cwd, char, len *= 2);
}

Моє рішення: повернути mallocрядок ed. Це просто, надійно і не менш ефективно. За винятком вбудованих платформ та старих систем, mallocнасправді досить швидко.


4
Я б не назвав це поганою практикою, я б назвав цю хорошу практику. 1) Це настільки звично, що жоден програміст не повинен дивуватися цим. 2) Це залишає розподіл для абонента, що виключає численні можливості помилок витоку пам'яті. 3) Він сумісний зі статично виділеними буферами. 4) Це робить реалізацію функції чистішою, функція, що обчислює якусь математичну формулу, не повинна стосуватися чогось зовсім не пов'язаного, наприклад, динамічного розподілу пам'яті. Ви думаєте, що main стає чистішим, але функція стає messier. 5) malloc заборонено навіть у багатьох системах.

@Lundin: Проблема в тому, що це призводить до того, що програмісти створюють непотрібні жорстко обмежені обмеження, і їм доводиться дуже старатися, щоб цього не робити (див. Приклад вище). Це добре для таких речей snprintf(buf, 32, "%d", n), де довжина виводу прогнозована (звичайно, не більше 30, якщо intце справді величезна кількість у вашій системі). Дійсно, malloc недоступний у багатьох системах, але для робочого та серверного середовища він є, і він працює дуже добре.
Joey Adams

Але проблема полягає в тому, що функція у вашому прикладі не встановлює жорстко обмежених обмежень. Цей код не є звичайною практикою. Тут головне знає про довжину буфера, яку повинна знати функція. Все це говорить про поганий дизайн програми. Головний, здається, не знає, що навіть функція getcwd виконує, тому для з'ясування використовує певну "грубу силу". Десь інтерфейс між модулем, в якому проживає getcwd, і абонент заплутаний. Це не означає, що такий спосіб виклику функцій поганий, навпаки, досвід показує, що він хороший з причин, які я вже перераховував.

1

Функції, які приймають / повертають складові типи даних за значенням або використовують зворотні виклики.

Ще гірше, якщо згаданий тип є об'єднанням або містить бітові поля.

З точки зору того, хто телефонує на C, це насправді нормально, але я не пишу на C або C ++, якщо цього не потрібно, тому я зазвичай телефоную через FFI. Більшість FFI не підтримують об'єднання або бітові поля, а деякі (наприклад, Haskell і MLton) не можуть підтримувати структури, передані за значенням. Для тих, хто може управляти структурами за значенням, принаймні загальні Lisp та LuaJIT змушені проходити повільними шляхами - загальний зовнішній функціональний інтерфейс Lisp повинен здійснювати повільний дзвінок через libffi, і LuaJIT відмовляється JIT-компілювати шлях коду, що містить виклик. Функції, які можуть передзвонити в хости, також запускають повільні шляхи на LuaJIT, Java та Haskell, при цьому LuaJIT не може зібрати такий виклик.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.