文字列を返す関数、良いスタイル?

Cプログラムでは、ADTの文字列表現を作成する方法が必要になることがよくあります。文字列を画面に出力する必要がない場合でも、このようなデバッグ方法があると便利です。そのため、この種の関数が頻繁に表示されます。

char * mytype_to_string( const mytype_t *t ); 

文字列が返されるメモリを処理するための(少なくとも)3つのオプションがあることに実際に気づきました。

代替1:関数の静的char配列に返される文字列を格納する。呼び出しのたびに文字列が上書きされることを除いて、あまり考える必要はありません。これは場合によっては問題になる可能性があります。

代替2:関数内でmallocを使用してヒープに文字列を割り当てます。バッファのサイズや上書きについて考える必要がないので、本当にすっきりしています。ただし、完了したら文字列をfree()することを忘れないでください。また、次のような一時変数に割り当てる必要があります。解放できます。ヒープ割り当てはスタック割り当てよりも非常に遅いため、これがループで繰り返されるとボトルネックになります。

代替3:ポインタをバッファに渡して、呼び出し元に割り当てさせます。そのバッファ。例:

char * mytype_to_string( const mytype_t *mt, char *buf, size_t buflen ); 

これにより、呼び出し元により多くの労力がもたらされます。また、この代替手段により、引数の順序に別のオプションが与えられることにも気付きました。最初と最後のどちらの議論が必要ですか?(実際には6つの可能性)

では、どちらを優先する必要がありますか?なぜですか?C開発者の間で何らかの未記述の標準がありますか?

コメント

  • 注意点として、ほとんどのオペレーティングシステムはオプション3を使用します-呼び出し元はとにかくバッファを割り当てます;バッファポインタと容量を通知します;呼び出し先はバフを埋めますerであり、バッファが不十分な場合は文字列の実際の長さも返します。例:OSXおよびiOSの sysctlbyname

回答

私が最もよく見たメソッドは2と3です。

ユーザーが提供するバッファーは、実際には非常に簡単に使用できます。

char[128] buffer; mytype_to_string(mt, buffer, 128); 

ほとんどの実装では、使用されているバッファの量が返されます。

オプション2は遅くなり、動的にリンクされたライブラリを使用する場合は危険です。異なるランタイム(および異なるヒープ)。したがって、別のライブラリでmallocされたものを解放することはできません。これを処理するには、free_string(char*)関数が必要です。

コメント

  • ありがとうございます。オルタナティブ3も一番好きだと思います。ただし、printf("MyType: %s\n", mytype_to_string( mt, buf, sizeof(buf));のようなことができるようにしたいので、'使用した長さではなく、ポインタを返します。文字列に。ダイナミックライブラリのコメントは非常に重要です。
  • ' sizeof(buffer) - 1は、\0ターミネータ?
  • @ Michael-Oバッファサイズにnull項が含まれていません。つまり、入力できる最大文字列は、渡されたサイズより1小さいです。これは、snprintfのような標準ライブラリで安全な文字列が機能するパターンです。
  • @ratchetfreak説明していただきありがとうございます。その知恵で答えを拡張するといいでしょう。

答え

#3の追加の設計アイデア

可能であれば、mytype mytype_to_string()と同じ.hファイル内。

#define MYTYPE_TO_STRING_SIZE 256 

これで、ユーザーはそれに応じてコーディングできます。

char buf[MYTYPE_TO_STRING_SIZE]; puts(mytype_to_string(mt, buf, sizeof buf)); 

注文

配列のサイズは、最初はVLAタイプを考慮に入れています。

char * mytype_to_string( const mytype_t *mt, size_t bufsize, char *buf[bufsize]); 

1つのディメンションではそれほど重要ではありませんが、2つ以上では便利です。

void matrix(size_t row, size_t col, double matrix[row][col]); 

次のCでは、最初にサイズを設定して読むことが好ましいイディオムであることを思い出します。その参照を見つける必要があります…

回答

@ratchetfreakの優れた答えに加えて、代替案#3は標準Cライブラリ関数と同様のパラダイム/パターンに従っていることを指摘しておきます。

たとえば、strncpy

 char * strncpy ( char * destination, const char * source, size_t num );  

同じパラダイムに従うと役立ちます新しい開発者(または将来の自分)があなたの機能を使用する必要があるときに、認知的負荷を軽減するためです。

投稿にあるものとの唯一の違いは、引数は、引数リストの最初にリストされる傾向があります。つまり:

 char * mytype_to_string( char *buf, const mytype_t *mt, size_t buflen );  

回答

ほとんどの場合、@ ratchet_freakをエコーします(sizeof buffer128に少し調整します)しかし、私は奇妙な応答でここに飛び込みたいです。奇妙なことはどうですか?同僚から奇妙な外見を取得し、さらに説得力を持たなければならないという問題に加えて、なぜですか?そして私はこれを提供します:

// Note allocator parameter. char* mytype_to_string(allocator* alloc, const mytype_t* t) { char* buf = allocate(alloc, however_much_you_need); // fill out buf based on "t" contents return buf; } 

使用例:

void func(my_type a, my_type b) { allocator alloc = allocator_new(); const char* str1 = mytype_to_string(&alloc, &a); if (!str1) goto oom; const char* str2 = mytype_to_string(&alloc, &b); if (!str2) goto oom // do something with str1 and str2 goto finish; oom: errno = ENOMEM; finish: // Frees all memory allocated through `alloc`. allocator_purge(&alloc); } 

このようにすると、アロケーターを非常に効率的に(より効率的に)することができます。 mallocよりも、割り当て/割り当て解除のコストと、メモリアクセスの参照の局所性の向上の両方の点で)。割り当て要求の一般的なケースでポインタをインクリメントするだけのアリーナアロケータにすることができます。大きな連続ブロックから順次メモリをプールします(最初のブロックはヒープを必要としません)場所-スタックに割り当てることができます)。エラー処理を簡素化します。また、これは最も議論の余地があるかもしれませんが、渡したアロケータにメモリを割り当て、明示的な解放が必要であることを呼び出し元に明確にする方法に関しては、実質的に非常に明白だと思います(allocator_purgeこの場合)このスタイルを一貫して使用する場合、考えられるすべての関数でそのような動作を文書化する必要はありません。アロケータパラメータを受け入れることで、うまくいけば非常に明白になります。

わかりません。可能な限り最も単純なアリーナアロケータを実装する(すべてのリクエストに最大の配置を使用する)などの反論があります。それに対処するのは大変な作業です。私の率直な考えは、Pythonプログラマーは何ですか?もしそうならPythonを使用することもできます。これらの詳細が重要でない場合はPythonを使用してください。私は「真面目です。Cプログラミングの同僚がたくさんいて、より正確なコードを書くだけでなく、参照の局所性などを無視して左右に発生するバグに遭遇するため、Pythonでさらに効率的になる可能性があります。私はしません。」データの局所性や最適な命令選択などに関心のあるCプログラマーの場合、ここにある単純なアリーナアロケーターについてはとても怖いです。これは、少なくとも必要なインターフェイスの種類よりも、おそらくはるかに少ないです。呼び出し元は、インターフェイスが返すことができるすべての個別のものを明示的に解放します。これにより、エラーが発生しやすい種類の個別の割り当て解除よりも一括割り当て解除が提供されます。適切なCプログラマーは、特にプロファイラーにホットスポットとして表示されている場合に、mallocへのループ呼び出しに挑戦します。私の見解では、2020年もCプログラマーであり続けるための論理的根拠には、" oomph "がもっと必要です。もうメモリアロケータのようなものから遠ざかっています。

これには、たとえば256バイトを割り当てる固定サイズのバッファを割り当てるというエッジケースがなく、結果の文字列が大きくなります。関数がバッファオーバーランを回避している場合でも(sprintf_sのように)、必要なエラーから適切に回復するためのコードがさらにあります。これは、アロケーターの場合は省略できます。 「これらのエッジケースはありません。アロケータケースでこのようなケースに対処する必要はありません。ただし、ハードウェアの物理的なアドレス指定スペースを本当に使い果たしていない限り(上記のコードは処理しますが、"事前に割り当てられたバッファ"とは別に"メモリ不足")。

回答

あなたが提案しているのは、悪いコードの臭い、代替3は私には最適に聞こえます。また、@ gnasher729のように間違った言語を使用していると思います。

コメント

回答

正直なところ、文字列を返すことが複雑で、作業量が多く、エラーが発生しやすい操作ではない別の言語に切り替えることをお勧めします。

コードの99%を変更せずに残すことができるC ++またはObjective-Cを検討できます。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です