CでのAPI設計の落とし穴[終了]


10

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

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

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

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


3
ジョーイ、この質問は人々が嫌うもののリストを作成するように頼むことによって建設的ではないことに迫っています。回答が彼らが指摘している実践がなぜ悪いのかを説明し、それらを改善する方法に関する詳細な情報を提供する場合、質問が役立つ可能性があります。そのために、例を質問から独自の回答に移動し、それがなぜ問題であるか、およびmalloc'd文字列がそれを修正する方法を説明してください。最初の答えで良い例を示すことは、この質問を成功させるのに本当に役立つと思います。ありがとう!
アダムリア

1
@Anna Lear:なぜ私の質問に問題があったの教えてくれてありがとう。私は、例を求めて代替案を提案することで、それを建設的に保つことを試みていました。自分が何を考えていたかを示すために、いくつかの例が本当に必要だったと思います。
ジョーイアダムス

@ジョーイ・アダムスこのように見てください。一般的な方法でC APIの問題を「自動的に」解決することになっている質問をしています。StackOverflowなどのサイトは、プログラミングに関するより一般的な問題を簡単に見つけて回答できるように設計されています。StackOverflowは当然、質問に対する回答のリストになりますが、より構造化された簡単に検索可能な方法になります。
Andrew T Finnell、2011

私は自分の質問を閉じるために投票しました。私の目標は、新しいCライブラリに対するチェックリストとして機能する回答のコレクションを用意することでした。これまでの3つの回答はすべて、「矛盾」、「非論理的」、「混乱」などの単語を使用しています。APIがこれらの回答に違反しているかどうかを客観的に判断することはできません。
Joey Adams

回答:


5

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

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

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


2
実際には、time_tは(通常)署名されています。ただし、1969年12月31日を「無効」と呼ぶことは、かなり非論理的です。私は深刻では、溶液は同様に、エラーコードを返し、ポインタを通して結果を渡すであろう:-) 60代は粗た推測:int time(time_t *out);およびBOOL CreateFile(LPCTSTR lpFileName, ..., HANDLE *out);
ジョーイアダムス

丁度。time_tが署名されていない場合は奇妙であり、time_tが署名されている場合は、有効なものの海の中で1回無効になります。
David Schwartz、

4

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

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

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


1
condの中には、pthread_cond_wait「条件付きで待つ」という意味ではありません。これは、条件変数で待機しているという事実を指します
Jonathon Reinhart、2015

そうです、それは条件に対する無条件の待機です
David Schwartz

3

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

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

  • 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);

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);
}

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


4
私はこれを悪い習慣とは呼びません、私はこの良い習慣と呼びます。1)まったく一般的であるため、プログラマーが驚かないでください。2)割り当ては呼び出し側に任され、メモリリークバグの多数の可能性が排除されます。3)静的に割り当てられたバッファと互換性があります。4)関数の実装がよりクリーンになります。数式を計算する関数は、動的メモリ割り当てなど、まったく無関係なものに関係するべきではありません。mainはよりクリーンになると思いますが、関数は乱雑になります。5)多くのシステムでは、mallocは許可されていません。

@Lundin:問題は、プログラマーが不必要にハードコードされた制限を作成することにつながり、彼らはそうしないように本当に努力しなければならないことです(上記の例を参照)。以下のようなもののためにそれの罰金snprintf(buf, 32, "%d", n)(場合を除き、確かに30以下の出力の長さは予測可能であり、intある本当にあなたのシステム上の巨大な)。確かに、mallocは多くのシステムでは利用できませんが、デスクトップおよびサーバー環境では利用可能であり、非常にうまく機能します。
ジョーイアダムス

しかし問題は、例の関数がハードコードされた制限を設定しないことです。このようなコードは一般的な方法ではありません。ここでは、mainは関数が知っておくべきバッファ長についての事柄を知っています。それはすべて、貧弱なプログラム設計を示唆しています。Mainはgetcwd関数が何をするのかさえ知らないようで、それを見つけるために「ブルートフォース」割り当てを使用しています。getcwdが常駐するモジュールと呼び出し元の間のインターフェースが混乱しています。これは、関数を呼び出すこの方法が悪いことを意味するものではありません。逆に、私がすでに挙げた理由から、それが良いことを示しています。

1

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.