web-dev-qa-db-ja.com

CでのAPI設計の落とし穴

C API(標準ライブラリ、サードパーティのライブラリ、プロジェクト内のヘッダーなど)に不快感を与える欠陥は何ですか?目標は、CでのAPI設計の落とし穴を特定することです。これにより、新しいCライブラリを作成するユーザーは、過去の過ちから学ぶことができます。

欠陥が(できれば例とともに)悪い理由を説明し、改善を提案してみてください。あなたのソリューションは実際には実用的ではないかもしれませんが(strncpyを修正するには遅すぎます)、将来のライブラリライターのために準備を整える必要があります。

この質問の焦点はC APIですが、他の言語でそれらを使用する能力に影響を与える問題は大歓迎です。

民主主義が回答を分類できるように、回答ごとに1つの欠陥を指定してください。

10
Joey Adams

一貫性のない、または非論理的な戻り値を持つ関数。 2つの良い例:

1)HANDLEを返す一部のWindows関数は、エラー(CreateThread)にNULL/0を使用し、エラー(CreateFile)にINVALID_HANDLE_VALUE/-1を使用するものがあります。

2)POSIXの 'time'関数はエラー時に '(time_t)-1'を返します。これは、 'time_t'が符号付きまたは符号なしの型になる可能性があるため、実際には非論理的です。

5
David Schwartz

説明的ではない、または明確に混乱する名前の関数またはパラメーター。例えば:

1)Windows APIのCreateFileは実際にはファイルを作成せず、ファイルハンドルを作成します。パラメータを介して要求された場合、 'open'と同様にファイルを作成できます。このパラメータには、「CREATE_ALWAYS」および「CREATE_NEW」と呼ばれる値があり、その名前からもセマンティクスはわかりません。 (「CREATE_ALWAYS」は、ファイルが存在する場合に失敗することを意味しますか?それともその上に新しいファイルを作成しますか?「CREATE_NEW」は常に新しいファイルを作成し、ファイルがすでに存在する場合は失敗することを意味しますか?それとも新しいファイルを作成しますか?その上にファイル?)

2)POSIX pthreads APIのpthread_cond_waitは、その名前にもかかわらず無条件待機です。

4
David Schwartz

タイプ削除ハンドルとしてインターフェースを介して渡される不透明タイプ。もちろん問題は、コンパイラーがユーザーコードをチェックして正しい引数の型を確認できないことです。

これには、次のようなさまざまな形式とフレーバーがあります。

  • void*虐待

  • intをリソースハンドルとして使用(例:CDIライブラリ)

  • 文字列型 引数

より明確なタイプ(=完全に互換的に使用することはできません)は、同じタイプの削除されたタイプにマップされ、悪化します。もちろん、救済策は単純に(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);

一貫性がなく、多くの場合面倒な文字列を返す規則を持つ関数。

たとえば、 getcwd は、ユーザー指定のバッファーとそのサイズを要求します。これは、アプリケーションが現在のディレクトリ長に任意の制限を設定するか、次のようにする必要があることを意味します( from 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);
}

私の解決策:malloced文字列を返します。シンプルで堅牢、そして効率も優れています。組み込みプラットフォームと古いシステムを除いて、mallocは実際には非常に高速です。

2
Joey Adams

値によって複合データ型を取得/返す関数、またはコールバックを使用する関数。

上記の型が共用体であるか、ビットフィールドを含んでいる場合はさらに悪いことです。

Cの呼び出し元の観点からは、これらは実際には問題ありませんが、必要でない限り、CまたはC++で記述しないので、通常はFFI経由で呼び出します。ほとんどのFFIは共用体またはビットフィールドをサポートしておらず、一部(HaskellやMLtonなど)は値で渡される構造体をサポートできません。値による構造体を処理できる場合、少なくともCommon LISPとLuaJITは低速パスに強制されます。LISPのCommon Foreign Function Interfaceはlibffiを介して低速の呼び出しを行う必要があり、LuaJITは呼び出しを含むコードパスのJITコンパイルを拒否します。ホストにコールバックする可能性のある関数も、LuaJIT、Java、およびHaskellで低速パスをトリガーし、LuaJITはそのような呼び出しをコンパイルできません。

1
Demi