C コーディングの経験の中で、関数の引数を渡す 2 つの方法を見てきました。
malloc
関数を呼び出す前にmalloc
関数内 (変数は関数を呼び出す前に初期化されません)
私は、特に、2 番目の形式を好みます。しかし、プログラムをコーディングするのは私だけですが、それを知っている人もいますが、他の人はそれを知ることができず、 2malloc
やメモリリークにつながる可能性があります。
だから、私の質問は次のとおりです。これのベストプラクティスは何ですか?
呼び出し元でメモリを割り当てると、呼び出し元が動的割り当ての代わりに静的または自動ストレージを使用できるようになり、呼び出し先で割り当てが失敗した場合を処理する必要がなくなるため、より柔軟になります。一方、呼び出し元にストレージを提供させるには、呼び出し元が事前にサイズを知る必要があります。サイズが定数として呼び出し元にコンパイルされ、呼び出し先が後で更新されてより大きな構造を使用するライブラリにある場合、事態はひどく壊れます。もちろん、必要なサイズを取得するための 2 番目の関数 (またはライブラリ内の外部変数) を提供することで、これを回避できます。
疑問がある場合は、いつでも次の 2 つの関数を作成できます。
その後、呼び出し元は、特定の使用例により適した方法を自由に選択できます。
個人的には、(可能な限り) 直交性に関する最初の提案を強く支持します。次の例を見てください。
extern void bar(int *p, int n);
void foo(int n)
{
int *p = malloc(n * sizeof *p);
// fill array object
bar(p, n);
// work with array elements
/* ... */
// array no longer needed, free object
free(p);
}
これは直交です。malloc
クリーンでfree
読みやすい同じレキシカルスコープで呼び出されます。bar
もう 1 つの利点は、たとえば、自動または静的なストレージ期間を持つ配列など、異なるストレージ期間を持つ配列を関数に渡すことができることです。関数はそれが行った作業のみに集中させbar
、別の関数に配列の割り当てを管理させます。
これは、すべての標準 C 関数の動作方法でもあることに注意してくださいmalloc
。
私が決定するために使用する基準は次のとおりです。
呼び出された関数の外側のコードが割り当てるメモリの量を認識できる場合は、呼び出し元のコードにメモリを割り当てることをお勧めします。
呼び出された関数の外部のコードが割り当てるメモリの量を認識できない場合、呼び出された関数がメモリの割り当てを行う必要があります。その場合、最初の関数 (「呼び出された」関数) によって返されたメモリを解放するために使用できる 2 番目の関数が存在する可能性がありfree()
ます。関数のドキュメントでこれを明確にする必要があります。
たとえば、呼び出された関数がファイルから完全なツリー構造を読み取っている場合、関数はメモリを割り当てる必要があります。ただし、メモリを解放するためのコンパニオン関数もあります (呼び出されたコードはそれを行う方法を知っており、呼び出したコードは知る必要がないため)。
一方、呼び出された関数が整数値と浮動小数点値の単純なリストを固定サイズの構造体に読み取っている場合は、呼び出し元の関数にメモリを割り当てさせる方がはるかに優れています。「文字列」をスキップしたことに注意してください。文字列が構造内で固定サイズの場合、呼び出し元の関数が割り当てを行うことができますが、文字列が可変サイズの場合は、おそらく呼び出された関数が割り当てを行います。
標準 C ライブラリにはfgets()
、呼び出し元のコードが使用するメモリを割り当てることを期待するような関数があります。呼び出しシーケンスfgets()
は、使用可能なスペースの量を示します。十分なメモリを提供しないと、問題が発生します。(問題fgets()
は、テキスト行全体ではなく、テキスト行の先頭のみを取得する可能性があることです。)
POSIX 2008 ライブラリはgetline()
、行に十分なスペースを割り当てるものを提供します。
およびasprintf()
関連する関数 ( TR24731-2を参照) は、必要に応じてメモリを割り当てます。このsnprintf()
関数はそうではありません — 利用可能なスペースがどれだけあるか、それ以上は使用せず、実際にどれだけ必要かを示します。十分なスペースを提供しなかったかどうかを確認して何かを行うのはあなた次第です。 (より多くのスペースを割り当てて再試行するか、切り捨てられた値を軽々しく無視して、何も問題がなかったかのように続行します)。
情報隠蔽の原則は、メモリの割り当ては関数内で行うのが最適であることを示唆しています。
stdio.hの仕組みを見ると、次のようになります。
FILE *myFile;
myFile = fopen("input.txt", "r");
if (!myFile) {
fprintf(stderr, "Error opening input.txt for reading.\n");
// other exit handling close
}
else {
// code to read from file
fclose(myFile);
}
ライブラリ呼び出しは、作業中のファイルに関する情報を保持するメモリを割り当て、その構造体へのポインタを返します。呼び出し元は、後でそのメモリを解放する責任があります ( fcloseの呼び出しを使用)。
このパターンは、標準 C ライブラリ全体で繰り返されます。
呼び出し元にメモリの割り当てと解放を要求することには、少なくとも 2 つの欠点があります。