情報隠蔽
関数のreturnステートメントで構造全体を返すのではなく、構造へのポインターを返すことの利点は何ですか?
最も一般的なのは情報隠蔽です。Cには、たとえば、struct
プライベートのフィールドを作成する機能はなく、それらにアクセスするメソッドは提供していません。
したがって、開発者がのように指示先の内容を強制的に表示および改ざんできないようにしたい場合FILE
、唯一の方法は、指示先のサイズが定義は外の世界には知られていない。の定義はFILE
、のようにfopen
、その定義を必要とする操作を実装している人にのみ表示されますが、パブリックヘッダーには構造体宣言のみが表示されます。
バイナリ互換性
構造定義を非表示にすると、dylib APIのバイナリ互換性を維持するための余裕を提供できます。ライブラリの実装者は、ライブラリの使用者とのバイナリ互換性を損なうことなく、不透明な構造のフィールドを変更できます。コードの性質は、構造の大きさやフィールドではなく、構造で何ができるかを知るだけでよいためです。持っています。
例として、今日のWindows 95時代に構築されたいくつかの古代のプログラムを実際に実行できます(常に完全ではありませんが、驚くほど多くがまだ動作しています)。古代のバイナリのコードの一部では、Windows 95時代からサイズと内容が変更された構造体への不透明なポインターが使用されていた可能性があります。それでも、プログラムはこれらの構造のコンテンツにさらされていないため、新しいバージョンのウィンドウで動作し続けます。バイナリ互換性が重要なライブラリで作業する場合、クライアントが公開されていないものは一般に、後方互換性を損なうことなく変更できます。
効率
NULLである完全な構造体を返すのは、私にとっては難しいか、効率が悪いでしょう。これは正当な理由ですか?
malloc
既に割り当てられている可変サイズのアロケーターではなく、固定サイズのアロケーターのように、一般的にバックグラウンドで使用される一般化されたメモリーアロケーターよりもはるかに少ない一般的なメモリアロケーターがない限り、スタックが実際に適合し、スタックに割り当てられると仮定すると、通常は効率が低下します。この場合、ライブラリ開発者がに関連する不変式(概念的な保証)を維持できるようにすることは、この場合の安全性のトレードオフFILE
です。
少なくともパフォーマンスの観点からfopen
、ポインターを返すことNULL
は、ファイルを開くことができない場合にのみ返されるため、それほど有効な理由ではありません。それは、すべての一般的なケースの実行パスを遅くすることと引き換えに、例外的なシナリオを最適化することです。場合によっては、設計をより簡単にして、ポインターをNULL
返して、何らかの事後条件で返せるようにするための有効な生産性の理由があるかもしれません。
ファイル操作の場合、オーバーヘッドはファイル操作自体に比べて比較的些細なものであり、手動fclose
での回避は避けられません。私たちはクライアントに定義を露出させることにより(終値)のリソースを解放するの面倒保存することができますようではありませんそれはそうFILE
として値によってそれを返すにfopen
自身がヒープ割り当てを避けるために、またはファイル操作の相対的なコスト与えられたパフォーマンス向上の多くを期待します。
ホットスポットと修正
しかし、他のケースでは、malloc
不透明なポインターでこのプラクティスを頻繁に使用し、ヒープ上に必要以上に多くのものを割り当てた結果として、ホットスポットがあり、不要な強制キャッシュミスがあるレガシーコードベースで多くの無駄なCコードをプロファイルしました大きなループ。
私が代わりに使用する代替方法は、クライアントがそれらを改ざんすることを意図していない場合でも、構造定義を公開することです。
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
};
struct Foo foo_create(void);
void foo_destroy(struct Foo* foo);
void foo_something(struct Foo* foo);
将来的にバイナリ互換性の懸念がある場合、次のように将来の目的のために余分なスペースを余分に予約するだけで十分であることがわかりました:
struct Foo
{
/* priv_* indicates that you shouldn't tamper with these fields! */
int priv_internal_field;
int priv_other_one;
/* reserved for possible future uses (emergency backup plan).
currently just set to null. */
void* priv_reserved;
};
その予約済みスペースは少し無駄ですが、将来Foo
、ライブラリを使用するバイナリを壊さずにデータを追加する必要がある場合に、命を救うことができます。
私の意見では、情報の隠蔽とバイナリ互換性は、通常、可変長構造体以外の構造体のヒープ割り当てのみを許可する唯一の正当な理由です(これは常にそれを必要とするか、クライアントが割り当てなければならない場合は少なくとも少し厄介です) VLSを割り当てるためのVLA形式のスタック上のメモリ)。大きな構造体であっても、ソフトウェアがスタック上のホットメモリでより多くの作業を行うことを意味する場合、値で返す方が安価であることがよくあります。そして、彼らが作成の価値によって返すのが安くなかったとしても、簡単にこれを行うことができます:
int foo_create(struct Foo* foo);
...
/* In the client code: */
struct Foo foo;
if (foo_create(&foo))
{
foo_something(&foo);
foo_destroy(&foo);
}
... Foo
余分なコピーの可能性なしにスタックから初期化する。または、クライアントFoo
が何らかの理由で必要に応じてヒープに割り当てる自由さえあります。