return
で構造全体を返すのではなく、構造体へのポインタを返すことの利点は何ですか。関数のステートメント?
fopen
のような関数やその他の低レベルの関数について話していますが、構造体へのポインターを返す高レベルの関数もあるでしょう。
これは単なるプログラミングの質問ではなく、設計上の選択であると私は信じており、2つの方法の長所と短所についてもっと知りたいと思っています。
構造体へのポインタを返すことが利点だと思った理由は、NULL
ポインタを返すことで、関数が失敗したかどうかをより簡単に判断できるようにするためです。
NULL
である full 構造を返すことは、私が思うに難しいか、効率が悪くなります。これは正当な理由ですか?
コメント
回答
ありfopen
のような関数がstruct
タイプのインスタンスの代わりにポインタを返すいくつかの実用的な理由は次のとおりです。
-
struct
タイプの表現をユーザーから隠したい; - オブジェクトを動的に割り当てている;
- あなたは複数の参照を介してオブジェクトの単一のインスタンスを参照する;
FILE *
のようなタイプの場合、それはあなたがそうしないからですタイプの表現の詳細をユーザーに公開したい-FILE *
obje ctは不透明なハンドルとして機能し、そのハンドルをさまざまなI / Oルーチンに渡すだけです(FILE
は多くの場合struct
タイプ、それはある必要はありません)。
したがって、ヘッダーの不完全 struct
タイプをどこかに公開できます:
typedef struct __some_internal_stream_implementation FILE;
不完全な型のインスタンスを宣言することはできませんが、それへのポインターを宣言することはできます。したがって、FILE *
を作成し、fopen
、freopen
などを介して割り当てることができます。 、しかし、それが指すオブジェクトを直接操作することはできません。
fopen
関数がオブジェクトは、malloc
などを使用して動的に作成されます。その場合、ポインタを返すことは理にかなっています。
最後に、ある種の状態をstruct
オブジェクトに格納している可能性があり、その状態をいくつかの異なる場所で使用できるようにする必要があります。 struct
タイプのインスタンスを返した場合、それらのインスタンスはメモリ内の個別のオブジェクトになり、最終的に同期がとれなくなります。単一のオブジェクトへのポインタを返すことにより、全員が同じオブジェクトを参照します。
コメント
- ポインタをとして使用することの特別な利点不透明(OPAQUE)型は、構造自体がライブラリバージョン間で変更される可能性があり、’呼び出し元を再コンパイルする必要がないことです。
- @Barmar:確かに、ABIの安定性は Cの大きなセールスポイントであり、不透明なポインタがないと安定しません。
回答
「構造体を返す」には2つの方法があります。データのコピーを返すことも、参照(ポインタ)を返すこともできます。いくつかの理由から、一般にポインタを返す(そして一般的に渡す)ことが好ましいです。
まず、構造体のコピーは、ポインタのコピーよりもはるかに多くのCPU時間を要します。コードは頻繁に実行されるため、パフォーマンスに顕著な違いが生じる可能性があります。
次に、ポインタを何度コピーしても、メモリ内の同じ構造を指していることになります。それに対するすべての変更は、同じ構造に反映されます。ただし、構造自体をコピーしてから変更を加えると、その変更はそのコピーにのみ表示されます。別のコピーを保持するコードは変更を認識しません。まれに、これが必要な場合もありますが、ほとんどの場合、そうではなく、間違えるとバグが発生する可能性があります。
コメント
- ポインタで返すことの欠点:’そのオブジェクトの所有権を追跡する必要があります。それを解放します。また、ポインタの間接参照は、クイックコピーよりもコストがかかる場合があります。ここには多くの変数があるため、ポインターを使用する方が一般的に優れているわけではありません。
- また、最近のポインターは、ほとんどのデスクトップおよびサーバープラットフォームで64ビットです。 ‘私のキャリアの中で、64ビットに収まる構造体をいくつか見ました。したがって、’ 常にポインタのコピーは構造体のコピーよりもコストがかからないと言うことができます。
- これはほとんどの場合良い答えです。 、しかし私はその部分について同意しません時々、非常にまれに、これがあなたが望むものですが、ほとんどの場合それは’ではありません-まったく反対です。ポインタを返すと、いくつかの種類の望ましくない副作用が発生し、ポインタの所有権を誤って取得するためのいくつかの種類の厄介な方法が発生します。 CPU時間がそれほど重要ではない場合、私はコピーバリアントを好みます。それがオプションである場合、エラーが発生しにくいはるかにです。
- これは実際には注意が必要です。外部APIにのみ適用されます。内部関数の場合、過去数十年のわずかに能力のあるコンパイラーでさえ、大きな構造体を返す関数を書き直して、ポインターを追加の引数として取り、そこに直接オブジェクトを構築します。不変と可変の議論は十分に頻繁に行われてきましたが、不変のデータ構造があなたが望むものになることはほとんどないという主張は真実ではないことに同意できると思います。
- コンパイルの防火壁についても言及できます。ポインタのプロとして。広く共有されているヘッダーを持つ大規模なプログラムでは、関数を含む不完全な型により、実装の詳細が変更されるたびに再コンパイルする必要がなくなります。より良いコンパイル動作は、実際には、インターフェイスと実装が分離されたときに達成されるカプセル化の副作用です。値で返す(および渡す、割り当てる)には、実装情報が必要です。
回答
他の回答に加えて、 small struct
を値で返す価値がある場合があります。たとえば、1つのデータのペアと、それに関連するエラー(または成功)コードを返すことができます。
例を挙げると、fopen
は1つのデータ(開かれたFILE*
)で、エラーが発生した場合は、 errno
疑似グローバル変数。ただし、FILE*
ハンドルとエラーコード(次の場合に設定される)の2つのメンバーのstruct
を返す方がよいでしょう。ファイルハンドルはNULL
)です。歴史的な理由から、そうではありません(エラーは、今日はマクロであるerrno
グローバルを通じて報告されます)。
Go language には、2つ(またはいくつか)の値を返すための優れた表記法があります。
また、Linux / x86-64では ABI と呼び出し規約( x86-psABI ページを参照)は、struct
は、2つのレジスタを介して返されます(これは非常に効率的で、メモリを通過しません)。
したがって、新しいCコードでは、小さなC struct
を返すと、読みやすく、スレッドフレンドリーになり、効率が向上します。
コメント
- 実際には、小さな構造体は
rdx:rax
にパックされます。したがって、struct foo { int a,b; };
はrax
にパックされて返され(たとえば、shift / orを使用)、shift / movを使用してアンパックする必要があります。 こちら’はGodboltの例です。ただし、x86は64ビットレジスタの下位32ビットを32ビット操作に使用でき、上位ビットを気にする必要がないため、’は常に悪すぎますが、使用するよりも明らかに悪いです2は、ほとんどの場合2メンバー構造に登録します。 - 関連: bugs.llvm.org/show_bug。cgi?id = 34840
std::optional<int>
はrax
の上半分にブール値を返すため、64ビットマスクが必要ですtest
でテストする定数。または、bt
を使用することもできます。ただし、dl
を使用する場合と比較すると、呼び出し元と呼び出し先にとっては問題があります。これは、コンパイラが”プライベート”関数。関連項目:libstdc ++ ‘ sstd::optional<T>
は’ tは簡単にコピーできません-Tが、したがって、常に非表示のポインタを介して返されます: stackoverflow.com/questions/46544019/ … 。 (libc ++ ‘は簡単にコピーできます) - @PeterCordes:関連するものはC ++でありCではありません
- おっと、そうです。呼び出し元がブール値をテストしたい場合、Cの
struct { int a; _Bool b; };
に正確に同じことが当てはまります。これは、簡単にコピーできるC ++構造体が同じABIを使用するためです。 C. - 典型的な例
div_t div()
回答
あなたは正しい方向に進んでいます
あなたが言及した両方の理由が有効です:
私が理由の1つ構造体へのポインタを返すことは、NULLポインタを返すことで関数が失敗したかどうかをより簡単に判断できるという利点があると考えました。
NULLであるFULL構造体を返すことは、私が思うに難しいでしょう。または効率が悪い。これは正当な理由ですか?
メモリのどこかにテクスチャがあり、そのテクスチャをメモリ内のいくつかの場所で参照したい場合プログラム;参照するたびにコピーを作成するのは賢明ではありません。代わりに、テクスチャを参照するためにポインタを渡すだけで、プログラムの実行速度が大幅に向上します。
最大の理由はは動的メモリ割り当てです。多くの場合、プログラムをコンパイルするときに、特定のデータ構造に必要なメモリの量が正確にわからないことがあります。これが発生した場合、使用する必要のあるメモリの量は実行時に決定されます。 mallocを使用してメモリを要求し、 freeの使用が終了したらメモリを解放します。
この良い例は、ユーザーが指定したファイルからの読み取りです。この場合、プログラムをコンパイルするときにファイルの大きさを把握します。プログラムが実際に実行されているときに必要なメモリ量しかわかりません。
mallocとfreeの両方がメモリ内の場所へのポインタを返します。したがって関数動的メモリ割り当てを利用すると、メモリ内に構造を作成した場所へのポインタが返されます。
また、コメントには、関数から構造体を返すことができるかどうかについての質問があることがわかりました。あなたは確かにこれを行うことができます。以下が機能するはずです:
struct s1 { int integer; }; struct s1 f(struct s1 input){ struct s1 returnValue = xinput return returnValue; } int main(void){ struct s1 a = { 42 }; struct s1 b= f(a); return 0; }
コメント
- メモリの量がわからないのはどうしてですか。構造体タイプがすでに定義されている場合、特定の変数が必要になりますか?
- @JenniferAnderson Cには、不完全なタイプの概念があります。タイプ名は宣言できますが、まだ定義されていないため、’のサイズは利用できません。そのタイプの変数を宣言することはできませんが、そのタイプへのポインタを宣言することはできます。
struct incomplete* foo(void)
。そうすれば、ヘッダーで関数を宣言できますが、Cファイル内の構造体のみを定義するため、カプセル化が可能になります。 - @amonしたがって、関数ヘッダー(プロトタイプ/署名)を宣言する前に、関数ヘッダー(プロトタイプ/署名)を宣言する方法を説明します。作業は実際にはCで行われますか?また、C
- @JenniferAndersonの構造体と共用体に対して同じことを行うことができます。ヘッダーファイルで関数プロトタイプ(本体のない関数)を宣言し、それらの関数を呼び出すことができます。他のコードでは、関数の本体を知らなくても、コンパイラは引数の配置方法と戻り値の受け入れ方法を知っている必要があるためです。プログラムをリンクするときまでに、実際には関数定義(つまり本体付き)を知っている必要がありますが、それを処理する必要があるのは1回だけです。単純でない型を使用する場合は、その型’の構造も知っておく必要がありますが、ポインターは同じサイズであることが多く、’プロトタイプ’の使用には関係ありません。
回答
FILE*
のようなものは、クライアントコードに関する限り、実際には構造体へのポインタではありませんが、代わりに、いくつかのに関連付けられた不透明な識別子の形式です。ファイルのような他のエンティティ。プログラムがfopen
を呼び出す場合、通常、返される構造の内容は気になりません。気になるのはfread
のような他の関数は、必要なことは何でも実行します。
標準ライブラリがFILE*
内に保持されている場合。そのファイル内の現在の読み取り位置であるfread
を呼び出すと、その情報を更新できる必要があります。 fread
にFILE
へのポインタを受信させると、簡単に実行できます。 fread
が代わりにFILE
を受け取った場合、FILE
オブジェクトを更新する方法はありません。発信者が保持します。
回答
情報の非表示
構造体全体を返すのではなく、構造体へのポインタを返すことの利点は何ですか。関数?
最も一般的なものは情報隠蔽です。 Cには、たとえば、struct
のフィールドをプライベートにする機能はなく、それらにアクセスするためのメソッドを提供することもできません。
したがって、強制的に実行したい場合は開発者がFILE
のようにポインティのコンテンツを表示したり改ざんしたりできないようにする場合、唯一の方法は、ポインタを処理して、ポインティがその定義にさらされるのを防ぐことです。ポインタのサイズと定義が外の世界に知られていない不透明なものとして。FILE
の定義は、、パブリックヘッダーには構造宣言のみが表示されます。
バイナリの互換性
構造定義を非表示にすると、dylib APIのバイナリ互換性を維持するための余裕ができます。これにより、ライブラリの実装者は不透明な構造のフィールドを変更できます。ライブラリを使用するユーザーとのバイナリ互換性を損なうことなく、コードの性質は、構造の大きさやフィールドの大きさではなく、構造で何ができるかを知るだけでよいためです。
たとえば、今日のWindows 95時代に構築されたいくつかの古いプログラムを実際に実行できます(必ずしも完全ではありませんが、驚くほど多くのプログラムがまだ機能しています)。これらの古代のバイナリのコードの一部は、Windows95の時代からサイズと内容が変更された構造体への不透明なポインタを使用していた可能性があります。それでも、プログラムはそれらの構造の内容に公開されていないため、新しいバージョンのWindowsで引き続き機能します。バイナリ互換性が重要なライブラリで作業する場合、クライアントが公開されていないものは通常、中断することなく変更できます。下位互換性。
効率
NULLである完全な構造を返すことは、私が思うに難しいか、効率が悪くなります。これは正当な理由ですか?
通常、タイプが実際に収まり、スタックに割り当てられると仮定すると、通常ははるかに少ない場合を除いて、効率が低下します。すでに割り当てられている可変サイズではなく固定サイズのアロケータプーリングメモリのように、malloc
よりも舞台裏で使用されている一般化されたメモリアロケータ。この場合、ほとんどの場合、安全性のトレードオフです。おそらく、ライブラリ開発者がFILE
に関連する不変条件(概念的な保証)を維持できるようにするためです。
少なくともパフォーマンスの観点からは、それほど正当な理由ではありません。 fopen
がポインタを返すようにするには、ファイルを開かなかったことがNULL
を返す唯一の理由です。それは、すべての一般的な実行パスを遅くすることと引き換えに、例外的なシナリオを最適化することです。場合によっては、設計をより単純にしてポインタを返し、NULL
を何らかの事後条件で返すことができるようにするための有効な生産性の理由がある可能性があります。
ファイル操作の場合、オーバーヘッドはファイル操作自体と比較して比較的非常に些細なものであり、手動でfclose
する必要はありません。したがって、FILE
の定義を公開し、それをまたは、ヒープ割り当てを回避するためのファイル操作自体の相対的なコストを考えると、パフォーマンスの大幅な向上が期待できます。
ホットスポットと修正
ただし、他の場合については、malloc
そして、不透明なポインタでこのプラクティスを頻繁に使用し、ヒープ上に、時には大きなループで不必要に多くのものを割り当てた結果として、不必要な強制キャッシュミスが発生します。
代わりに使用する別の方法は、クライアントが構造体を改ざんすることを意図していない場合でも、命名規則標準を使用して、他の誰もフィールドに触れてはならないことを伝えることにより、構造体定義を公開することです。
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; };
その予約済みスペースは少し無駄ですが、将来、ライブラリを使用するバイナリを壊さずに。
私の意見では、情報の隠蔽とバイナリの互換性が、通常、可変長構造体以外の構造体(クライアントがVLA fashでスタックにメモリを割り当てる必要がある場合は、常にそれを必要とするか、少なくとも使用するのが少し厄介です) VLSを割り当てるためのイオン)。大きな構造体でさえ、ソフトウェアがスタック上のホットメモリではるかに多く機能することを意味する場合、値で返す方が安いことがよくあります。また、作成時に値で返す方が安くなかったとしても、次のようにするだけで済みます。
int foo_create(struct Foo* foo); ... /* In the client code: */ struct Foo foo; if (foo_create(&foo)) { foo_something(&foo); foo_destroy(&foo); }
… は、余分なコピーの可能性なしにスタックから削除されます。または、クライアントは、何らかの理由で必要に応じて、ヒープにFoo
を割り当てることもできます。
gets()
関数が削除されるまでC11までかかりました。一部のプログラマーはまだ構造体をコピーすることに嫌悪感を持っており、古い習慣は一生懸命に死にます。FILE*
は事実上不透明なハンドルです。ユーザーコードは、その内部構造が何であるかを気にする必要はありません。&
で取得し、。”