9

ソース文字列へのポインターを受け入れ、宛先文字列へのポインターを返す文字列関数があります。この関数は現在機能していますが、malloc、realloc、および free を再評価するベスト プラクティスに従っていないのではないかと心配しています。

私の関数の違いは、宛先文字列の長さがソース文字列と同じではないため、関数内で realloc() を呼び出す必要があることです。ドキュメントを見ればわかるのですが…

http://www.cplusplus.com/reference/cstdlib/realloc/

再割り当て後にメモリアドレスが変更される可能性があることに注意してください。これは、C プログラマーが他の関数の場合のように「参照渡し」できないことを意味し、新しいポインターを返す必要があります。

したがって、私の関数のプロトタイプは次のとおりです。

//decode a uri encoded string
char *net_uri_to_text(char *);

関数を実行した後にポインターを解放する必要があるため、私はそれをやっている方法が好きではありません:

char * chr_output = net_uri_to_text("testing123%5a%5b%5cabc");
printf("%s\n", chr_output); //testing123Z[\abc
free(chr_output);

つまり、malloc() と realloc() は関数内で呼び出され、free() は関数外で呼び出されます。

私は高級言語 (perl、plpgsql、bash) のバックグラウンドを持っているので、私の本能はそのようなものを適切にカプセル化することですが、それは C でのベストプラクティスではないかもしれません.

質問: 私のやり方はベスト プラクティスですか、それとも従うべきより良い方法はありますか?

完全な例

未使用の argc および argv 引数に対して 2 つの警告を表示してコンパイルおよび実行します。これら 2 つの警告は無視しても問題ありません。

example.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char *net_uri_to_text(char *);

int main(int argc, char ** argv) {
  char * chr_input = "testing123%5a%5b%5cabc";
  char * chr_output = net_uri_to_text(chr_input);
  printf("%s\n", chr_output);
  free(chr_output);
  return 0;
}

//decodes uri-encoded string
//send pointer to source string
//return pointer to destination string
//WARNING!! YOU MUST USE free(chr_result) AFTER YOU'RE DONE WITH IT OR YOU WILL GET A MEMORY LEAK!
char *net_uri_to_text(char * chr_input) {
  //define variables
  int int_length = strlen(chr_input);
  int int_new_length = int_length;
  char * chr_output = malloc(int_length);
  char * chr_output_working = chr_output;
  char * chr_input_working = chr_input;
  int int_output_working = 0;
  unsigned int uint_hex_working;
  //while not a null byte
  while(*chr_input_working != '\0') {
    //if %
    if (*chr_input_working == *"%") {
      //then put correct char in
      sscanf(chr_input_working + 1, "%02x", &uint_hex_working);
      *chr_output_working = (char)uint_hex_working;
      //printf("special char:%c, %c, %d<\n", *chr_output_working, (char)uint_hex_working, uint_hex_working);
      //realloc
      chr_input_working++;
      chr_input_working++;
      int_new_length -= 2;
      chr_output = realloc(chr_output, int_new_length);
      //output working must be the new pointer plys how many chars we've done
      chr_output_working = chr_output + int_output_working;
    } else {
      //put char in
      *chr_output_working = *chr_input_working;
    }
    //increment pointers and number of chars in output working
    chr_input_working++;
    chr_output_working++;
    int_output_working++;
  }
  //last null byte
  *chr_output_working = '\0';
  return chr_output;
}
4

6 に答える 6

8

C の関数から 'd バッファーを返すmallocことは、その事実を文書化する限り、まったく問題ありません。標準ライブラリの関数はありませんが、多くのライブラリはそれを行います。

バッファに書き込む必要のある文字数を (あまり悲観的でない上限で) 安価に計算できる場合は、それを行う関数を提供して、ユーザーにそれを呼び出させることができます。

バッファーに入力することも可能ですが、あまり便利ではありません。私はそのようにするかなりの数のライブラリを見てきました:

/*
 * Decodes uri-encoded string encoded into buf of length len (including NUL).
 * Returns the number of characters written. If that number is less than len,
 * nothing is written and you should try again with a larger buffer.
 */
size_t net_uri_to_text(char const *encoded, char *buf, size_t len)
{
    size_t space_needed = 0;

    while (decoding_needs_to_be_done()) {
        // decode characters, but only write them to buf
        // if it wouldn't overflow;
        // increment space_needed regardless
    }
    return space_needed;
}

これで、呼び出し元が割り当てを担当し、次のようなことを行います

size_t len = SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH;
char *result = xmalloc(len);

len = net_uri_to_text(input, result, len);
if (len > SOME_VALUE_THAT_IS_USUALLY_LONG_ENOUGH) {
    // try again
    result = xrealloc(input, result, len);
}

(ここで、xmallocxreallocは、NULL チェックをスキップするために作成した "安全な" 割り当て関数です。)

于 2013-06-12T17:37:32.057 に答える
2

問題は、C が低レベルであるため、プログラマーがメモリ管理を正しく行う必要があるということです。malloc()特に、指定された文字列を返すことに問題はありません。不正なオブジェクトを返し、呼び出し元に渡すのは一般的なイディオムfree()です。

とにかく、このアプローチが気に入らない場合は、いつでも文字列へのポインターを取得して、関数内から変更できます (ただし、最後に使用した後は、まだfree()d にする必要があります)。

ただし、文字列を明示的に縮小することは必要ないと思います。新しい文字列が古い文字列よりも短い場合は、古い文字列のメモリ チャンクに明らかに十分なスペースがあるため、必要はありませんrealloc()

(もちろん、終端の NUL 文字に余分な 1 バイトを割り当てるのを忘れていたという事実は別として...)

そして、いつものように、関数が呼び出されるたびに異なるポインターを返すだけでよく、呼び出す必要さえありませんrealloc()

最後の良いアドバイスを 1 つ受け入れるとしconstたら、入力文字列を修飾することをお勧めします。このアプローチを使用すると、たとえば、文字列リテラルで関数を安全に呼び出すことができます。

全体として、関数を次のように書き直します。

char *unescape(const char *s)
{
    size_t l = strlen(s);
    char *p = malloc(l + 1), *r = p;

    while (*s) {
        if (*s == '%') {
            char buf[3] = { s[1], s[2], 0 };
            *p++ = strtol(buf, NULL, 16); // yes, I prefer this over scanf()
            s += 3;
        } else {
            *p++ = *s++;
        }
    }

    *p = 0;
    return r;
}

そして、次のように呼び出します。

int main()
{
    const char *in = "testing123%5a%5b%5cabc";
    char *out = unescape(in);
    printf("%s\n", out);
    free(out);

    return 0;
}
于 2013-06-12T17:38:39.030 に答える
2

malloc関数から新しく編集された(場合によっては内部的に編集された) 値を返すことはまったく問題reallocありません。(ここで行っているように) そうしていることを文書化する必要があるだけです。

その他の明白な項目:

  • 代わりにint int_lengthを使用することもできますsize_t。これは、文字列の長さと への引数に適した型である「符号なし型」 (通常はunsigned intまたは) です。unsigned longmalloc
  • 最初に n+1 バイトを割り当てる必要があります。ここで、n は文字列の長さでありstrlen、終端の 0 バイトは含まれません。
  • malloc失敗していないかどうかを確認する必要があります( を返しNULLます)。関数が失敗を渡す場合は、そのことを関数の説明のコメントに記録します。
  • sscanf2 つの 16 進バイトを変換するにはかなりの重量です。間違いではありませんが、変換が成功したかどうかをチェックしていないことを除いて (入力が不正な形式である場合はどうなりますか? もちろん、これは呼び出し元の問題であると判断できますが、一般的にはそれを処理したい場合があります)。isxdigitfromを使用し<ctype.h>て、16 進数をチェックしたりstrtoul、変換を実行したりできます。
  • realloc必要に応じて、変換ごとに1 つ実行するのではなく%、最終的な「縮小再割り当て」を実行することをお勧めします。文字列に (たとえば) 50 バイトを割り当て、最後の 0 バイトを含めて 49 バイトしか必要ないことがわかった場合、realloc結局のところ、実行する価値がない可能性があることに注意してください。
于 2013-06-12T17:38:01.287 に答える
0

さらに、他の投稿ですでに言及されていること、文字列が再割り当てされているという事実も文書化する必要があります。コードが静的文字列または で割り当てられた文字列で呼び出された場合、alloca再割り当てすることはできません。

于 2013-06-12T17:50:09.913 に答える
0

malloc と free の分割について心配するのは正しいと思います。原則として、それを作成するものは何でもそれを所有し、それを解放する必要があります。

この場合、文字列が比較的小さいため、文字列バッファーを、含まれる可能性のある文字列よりも大きくすることをお勧めします。たとえば、URL には約 2000 文字の事実上の制限があるため、10000 文字を malloc すると、可能な URL を格納できます。

もう 1 つのトリックは、文字列の長さと容量の両方を前面に格納すること(int)*mystring == length of stringです(int)*(mystring + 4) == capacity。したがって、文字列自体は 8 番目の位置からのみ開始し*(mystring+8)ます。これを行うことで、文字列への単一のポインタを渡すことができ、文字列の長さと文字列のメモリ容量を常に知ることができます。これらのオフセットを自動的に生成し、「きれいなコード」を作成するマクロを作成できます。

このようにバッファを使用することの価値は、再割り当てを行う必要がないことです。新しい値は古い値を上書きし、文字列の先頭で長さを更新します。

于 2013-06-12T17:51:53.520 に答える