22

strcpy()Cの機能が悪い、悪いと思っている人もいるようです。strncpy()通常はバッファ オーバーフローを回避するために使用する方がよいことは認めますが、次の (このstrdup()関数を使用することができなかった人のための関数の実装) は、安全に使用し、オーバーフローすることはstrcpy()ありません。

char *strdup(const char *s1)
{
  char *s2 = malloc(strlen(s1)+1);
  if(s2 == NULL)
  {
    return NULL;
  }
  strcpy(s2, s1);
  return s2;
}

*s2を保存するのに十分なスペースがあることが保証されており*s1、を使用するとstrcpy()、結果を別の関数に保存して、strlen()後で不要な (この場合) の長さパラメーターとして使用する必要がなくなりますstrncpy()。しかし、長さパラメータを必要とするや を使ってこの関数を書く人strncpy()もいます。memcpy()人々がこれについてどう思うか知りたいです。特定の状況で安全だと思う場合strcpy()は、そう言ってください。このような状況で使用しない正当な理由がある場合は、それを教えてください。このような状況で使用するstrcpy()方がよい理由を知りたいです。大丈夫だと思うが、ここにない場合は、説明してください。strncpy()memcpy()strcpy()

memcpy()基本的に、他の人が使用するときに使用する人strcpy()もいれば、プレーンを使用する人もいる理由を知りたいだけですstrncpy()。3つよりも1つを優先するロジックはありますか(最初の2つのバッファチェックを無視します)?

4

17 に答える 17

18

私はここのルールに従っています。そこから引用させてください

strncpyディレクトリエントリなどの構造体の固定長の名前フィールドを処理するために、C ライブラリに最初に導入されました。このようなフィールドは、文字列と同じようには使用されません。最大長のフィールドには末尾の null は不要であり、短い名前の末尾のバイトを null に設定すると、効率的なフィールド単位の比較が保証されます。strncpy はもともと「制限付き strcpy」ではありません。委員会は、関数をそのような用途により適したものに変更するよりも、既存の慣行を認識することを好みました。

そのため、これまでソース文字列から見つからない a'\0'をヒットしても、文字列に末尾が表示されることはありません。それを誤用するのは簡単です (もちろん、その落とし穴を知っていれば回避できます)。引用が言うように、制限された strcpy として設計されていません。また、必要がなければ使用しないことをお勧めします。あなたの場合、明らかにその使用は必要ではなく、あなたはそれを証明しました。なぜそれを使用するのですか?n'\0'

そして一般的に言えば、コードのプログラミングは冗長性を減らすことでもあります。「n」文字を含む文字列があることがわかっている場合、コピー機能に最大n文字数をコピーするように指示するのはなぜですか? 冗長なチェックを行います。パフォーマンスについてはほとんど問題ありませんが、一貫したコードについてはもっと重要です。読者はstrcpy、文字をまたぐ可能性のある何ができるのかn、コピーを制限する必要があるのか​​ を自問するでしょう。その場合、これは起こり得ないことをマニュアルで読むだけです。そして、コードの読者の間で混乱が始まります。

mem-str-またはを使用する合理性についてはstrn-、上記のリンクされたドキュメントのように、それらの中から選択しました。

mem-構造体のバイトなど、生のバイトをコピーしたいとき。

str-null で終了する文字列をコピーする場合 - 100% オーバーフローが発生しない場合のみ。

strn-null で終わる文字列をある長さまでコピーし、残りのバイトをゼロで埋めます。おそらく、ほとんどの場合、私が望むものではありません。末尾にゼロが埋め込まれているという事実を忘れがちですが、上記の引用で説明されているように、これは設計によるものです。したがって、文字をコピーする独自の小さなループをコーディングし、末尾に を追加し'\0'ます。

char * sstrcpy(char *dst, char const *src, size_t n) {
    char *ret = dst;
    while(n-- > 0) {
        if((*dst++ = *src++) == '\0')
            return ret;
    }
    *dst++ = '\0';
    return ret;
}

私が望むことを正確に行う数行。「生の速度」が必要な場合でも、この限定された strcpyジョブを正確に実行する、移植可能で最適化された実装を探すことができます。いつものように、最初にプロファイルを作成してから、いじります。

その後、C では、wcs-and wcsn-(for C99) と呼ばれる、ワイド文字を操作するための関数が得られました。私も同様に使用します。

于 2009-03-04T12:24:09.637 に答える
16

人々が strcpy ではなく strncpy を使用する理由は、文字列が常に null で終了するとは限らず、バッファ (strcpy で文字列に割り当てたスペース) を非常に簡単にオーバーフローさせて、無関係なメモリの一部を上書きしてしまうからです。

strcpy ではこれ発生する可能性があり、strncpy ではこれは発生しません。そのため、strcpy は安全ではないと見なされます。悪は少し強いかもしれません。

于 2009-03-04T12:10:09.437 に答える
11

strcpy率直に言って、C で多くの文字列処理を行っている場合、 orを使用する必要があるかどうかを自問する必要はありませstrncpymemcpy。より高いレベルの抽象化を提供する文字列ライブラリを見つけるか、作成する必要があります。たとえば、各文字列の長さを追跡し、メモリを割り当て、必要なすべての文字列操作を提供するものです。

これにより、バッファ オーバーフローや文字列を NUL バイトで終了するのを忘れるなど、通常 C 文字列の処理に関連する種類の間違いをほとんど起こさなくなることがほぼ確実になります。

ライブラリには、次のような関数が含まれている場合があります。

typedef struct MyString MyString;
MyString *mystring_new(const char *c_str);
MyString *mystring_new_from_buffer(const void *p, size_t len);
void mystring_free(MyString *s);
size_t mystring_len(MyString *s);
int mystring_char_at(MyString *s, size_t offset);
MyString *mystring_cat(MyString *s1, ...); /* NULL terminated list */
MyString *mystring_copy_substring(MyString *s, size_t start, size_t max_chars);
MyString *mystring_find(MyString *s, MyString *pattern);
size_t mystring_find_char(MyString *s, int c);
void mystring_copy_out(void *output, MyString *s, size_t max_chars);
int mystring_write_to_fd(int fd, MyString *s);
int mystring_write_to_file(FILE *f, MyString *s);

Kannel プロジェクト用に作成しました。gwlib/octstr.h ファイルを参照してください。それは私たちの生活をずっと簡単にしてくれました。一方で、このようなライブラリは非常に簡単に作成できるため、演習としてのみでも、自分用に作成することもできます。

于 2009-03-04T12:52:34.500 に答える
9

ToddC.MillerとTheodeRaadtによって開発されたstrlcpy誰も言及していません。彼らが彼らの論文で言うように:

最も一般的な誤解は、 strncpy()NULが宛先文字列を終了するというものです。ただし、これは、ソース文字列の長さがサイズパラメータよりも短い場合にのみ当てはまります。これは、任意の長さのユーザー入力を固定サイズのバッファーにコピーするときに問題になる可能性があります。この状況で使用する最も安全な方法 strncpy()は、宛先文字列のサイズより1つ小さいサイズを渡してから、手動で文字列を終了することです。そうすれば、常にNULで終了する宛先文字列を持つことが保証されます。

strlcpy;の使用には反論があります。ウィキペディアのページには、

Drepperは、それを主張しstrlcpystrlcatプログラマーが切り捨てエラーを無視しやすくするため、削除するよりも多くのバグを導入する可能性があります。*

ただし、これは、への引数の手動調整に加えて、手動のNULL終了を追加するために何をしているのかを知っている人々を強制するだけだと思いますstrncpy。を使用するstrlcpyと、バッファをNULLで終了できなかったため、バッファオーバーランを回避するのがはるかに簡単になります。

またstrlcpy、glibcまたはMicrosoftのライブラリがないことは、使用の障壁にはならないことに注意してください。BSDディストリビューションのソースや友人を見つけることができstrlcpy、ライセンスは商用/非商用プロジェクトに適している可能性があります。の上部にあるコメントを参照してくださいstrlcpy.c

于 2009-03-05T05:10:02.730 に答える
8

私は個人的に、コードが有効であることが証明できれば (そしてすぐに証明できれば)、それは完全に受け入れられると考えています。つまり、コードが単純で、明らかに正しい場合は問題ありません。

ただし、関数が実行されている間、他のスレッドが が指す文字列を変更しないと仮定しているようですs1。この関数がメモリ割り当て (したがって への呼び出しstrlen) に成功した後に中断された場合、文字列が大きくなり、 NULLstrcpyバイトにコピーされてからバッファ オーバーフロー状態になるとどうなりますか。

以下の方がよいかもしれません。

char *
strdup(const char *s1) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  return s2;
}

これで、ひもはあなた自身の過ちなく成長することができ、あなたは安全です. 結果は dup にはなりませんが、クレイジーなオーバーフローにもなりません。

あなたが提供したコードが実際にバグである可能性はかなり低いです (スレッド化がまったくサポートされていない環境で作業している場合、存在しないとは言わないまでも、存在しないにかなり近い)。それはちょうど考えるべきことです。

ETA : これは少し優れた実装です:

char *
strdup(const char *s1, int *retnum) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  retnum = s1_len;
  return s2;
}

そこに文字数が返されています。あなたもすることができます:

char *
strdup(const char *s1) {
  int s1_len = strlen(s1);
  char *s2 = malloc(s1_len+1);
  if(s2 == NULL) {
    return NULL;
  }

  strncpy(s2, s1, s1_len);
  s2[s1_len+1] = '\0';
  return s2;
}

NULバイトで終了します。どちらの方法でも、最初にすばやくまとめたものよりも優れています。

于 2009-03-04T12:06:49.647 に答える
5

同意します。strncpy()ただし、指定された長さに常に出力がパディングされるため、お勧めしません。これは歴史的な決定であり、パフォーマンスを著しく悪化させるため、本当に残念なことだと思います。

次のようなコードを検討してください。

char buf[128];
strncpy(buf, "foo", sizeof buf);

これは、期待される 4 文字をbufに書き込むのではなく、代わりに「foo」の後に 125 個のゼロ文字を書き込みます。たとえば、多くの短い文字列を収集している場合、これは実際のパフォーマンスが予想よりもはるかに悪いことを意味します。

可能であればsnprintf()、上記を次のように記述して使用することを好みます。

snprintf(buf, sizeof buf, "foo");

代わりに非定数文字列をコピーする場合は、次のように行います。

snprintf(buf, sizeof buf, "%s", input);

これは重要です。input% 文字snprintf()が含まれていると解釈され、ワー​​ムの缶の棚全体が開かれてしまうからです。

于 2009-03-04T12:15:29.550 に答える
5

strncpy も悪だと思います。

この種のプログラミング エラーから本当に身を守るには、(a) 問題ないように見え、(b) バッファをオーバーランするコードを記述できないようにする必要があります。

これは、バッファと容量を不透明に格納し、それらを永久に結合し、境界をチェックする、実際の文字列の抽象化が必要であることを意味します。そうしないと、文字列とその容量をショップ全体に渡すことになります。文字列の途中を変更するなど、実際の文字列操作に到達すると、strcpy を呼び出す宛先が小さすぎるのと同じくらい簡単に、間違った長さを strncpy (特に strncat) に渡します。

もちろん、その抽象化を実装する際に strncpy と strcpy のどちらを使用するべきか、まだ疑問に思うかもしれません。しかし、文字列を処理するアプリケーション コードでは、バッファ オーバーフローを防ぐために strncpy に依存することは、コンドームの半分を身に着けているようなものです。

そのため、strdup-replacement は次のようになります (サスペンスを維持するために定義の順序が変更されています)。

string *string_dup(const string *s1) {
    string *s2 = string_alloc(string_len(s1));
    if (s2 != NULL) {
        string_set(s2,s1);
    }
    return s2;
}

static inline size_t string_len(const string *s) {
    return strlen(s->data);
}

static inline void string_set(string *dest, const string *src) {
    // potential (but unlikely) performance issue: strncpy 0-fills dest,
    // even if the src is very short. We may wish to optimise
    // by switching to memcpy later. But strncpy is better here than
    // strcpy, because it means we can use string_set even when
    // the length of src is unknown.
    strncpy(dest->data, src->data, dest->capacity);
}

string *string_alloc(size_t maxlen) {
    if (maxlen > SIZE_MAX - sizeof(string) - 1) return NULL;
    string *self = malloc(sizeof(string) + maxlen + 1);
    if (self != NULL) {
        // empty string
        self->data[0] = '\0';
        // strncpy doesn't NUL-terminate if it prevents overflow, 
        // so exclude the NUL-terminator from the capacity, set it now,
        // and it can never be overwritten.
        self->capacity = maxlen;
        self->data[maxlen] = '\0';
    }
    return self;
}

typedef struct string {
    size_t capacity;
    char data[0];
} string;

これらの文字列の抽象化の問題は、誰も同意できないことです (たとえば、上記のコメントで言及されている strncpy の特異性が良いか悪いか、部分文字列を作成するときにバッファーを共有する不変文字列やコピーオンライト文字列が必要かどうかなど)。など)。そのため、理論上は既製の 1 つだけを使用する必要がありますが、プロジェクトごとに 1 つ使用することもできます。

于 2009-03-04T13:14:12.747 に答える
4

memcpy長さをすでに計算している場合は使用する傾向がありますstrcpyが、通常は機械語で動作するように最適化されていますが、最適なコピーメカニズムを使用できるように、ライブラリにできるだけ多くの情報を提供する必要があると感じています。

しかし、あなたが与える例では、それは問題ではありません-失敗する場合、それは initialstrlenになるため、 strncpy は安全性の観点から何も購入しません(そしてstrncpy、両方の境界をチェックする必要があるため、おそらく遅くなります)と nul の場合)、および と の違いはmemcpystrcpy投機的にコードを変更する価値はありません。

于 2009-03-04T12:01:48.037 に答える
4

人々がこのように使用すると、悪が起こります(ただし、以下は非常に単純化されています)。

void BadFunction(char *input)
{
    char buffer[1024]; //surely this will **always** be enough

    strcpy(buffer, input);

    ...
}

これは、驚くほど頻繁に発生する状況です。

しかし、ええ、strcpy は、宛先バッファーにメモリを割り当てていて、すでに strlen を使用して長さを見つけている状況では、strncpy と同じくらい優れています。

于 2009-03-04T12:07:43.310 に答える
1

strlen は、最後のヌル終了位置まで検索します。

しかし実際には、バッファは null で終了しません。

そのため、人々はさまざまな機能を使用しています。

于 2009-03-04T11:59:59.367 に答える
0
char* dupstr(char* str)
{
   int full_len; // includes null terminator
   char* ret;
   char* s = str;

#ifdef _DEBUG
   if (! str)
      toss("arg 1 null", __WHENCE__);
#endif

   full_len = strlen(s) + 1;
   if (! (ret = (char*) malloc(full_len)))
      toss("out of memory", __WHENCE__);
   memcpy(ret, s, full_len); // already know len, so strcpy() would be slower

   return ret;
}
于 2009-05-09T06:48:28.340 に答える
0

この回答では、高速でシンプルな を使用size_tしています。memcpy()strdup()

size_tから返され、 and によって使用される型であるため、typestrlen()を使用するのが最適です。 は、これらの操作に適した型ではありません。malloc()memcpy()int

memcpy()より遅いことはめったになくstrcpy()strncpy()多くの場合、大幅に高速です。

// Assumption: `s1` points to a C string.
char *strdup(const char *s1) {
  size_t size = strlen(s1) + 1;
  char *s2 = malloc(size);
  if(s2 != NULL) {
    memcpy(s2, s1, size);
  }
  return s2;
} 

§7.1.1 1 「文字列は、最初の null 文字で終了し、最初の null 文字を含む連続した文字列です。...」

于 2014-04-21T17:02:34.093 に答える
0

まあ、strcpy() は strdup() ほど悪くはありません - 少なくとも strcpy() は標準 C の一部です。

于 2009-03-04T12:16:20.280 に答える
0
char *strdup(const char *s1)
{
  char *s2 = malloc(strlen(s1)+1);
  if(s2 == NULL)
  {
    return NULL;
  }
  strcpy(s2, s1);
  return s2;
}

問題:

  1. s1 が終了せず、strlen が未割り当てメモリへのアクセスを引き起こし、プログラムがクラッシュします。
  2. s1 は終了していませんが、アプリケーションの別の部分から未割り当てのメモリ アクセス メモリにアクセスすることはありません。ユーザーに返されるか (セキュリティの問題)、プログラムの別の部分によって解析されます (heisenbug が表示されます)。
  3. s1 は終了していません。strlen は、システムが満たすことができない malloc になり、NULL を返します。strcpy に NULL が渡されると、プログラムがクラッシュします。
  4. s1 は終了せず、strlen は非常に大きな malloc を生成し、システムは目の前のタスクを実行するにはあまりにも多くのメモリを割り当て、不安定になります。
  5. コードが効率的でない場合でも、strlen は文字列内のすべての要素にアクセスする必要があります。

おそらく他にも問題があります...ほら、ヌル終了は必ずしも悪い考えではありません。計算効率のために、またはストレージ要件を減らすために、それが理にかなっている状況があります。

ビジネスロジックなどの汎用コードを書く場合、それは理にかなっていますか? いいえ。

于 2011-09-27T22:07:31.897 に答える
0

あなたが説明した状況では、 strcpy は良い選択です。この strdup は、s1 が '\0' で終わっていない場合にのみ問題になります。

strcpy に問題がない理由を示すコメントを追加して、他の人 (および今から 1 年後の自分) がその正確性について長い間疑問に思うのを防ぎます。

多くの場合、strncpy は安全に見えますが、問題が発生する可能性があります。ソースの「文字列」がカウントより短い場合、カウントに達するまでターゲットを「\0」で埋めます。それはパフォーマンスに悪いかもしれません。ソース文字列が count より長い場合、strncpy はターゲットに '\0' を追加しません。'\0' で終わる "文字列" を予期したときに、後で問題が発生することは間違いありません。そのため、strncpy も注意して使用する必要があります。

「\0」で終了する文字列を使用していない場合にのみ memcpy を使用しますが、それは好みの問題のようです。

于 2009-03-04T12:49:56.627 に答える
-1

あなたのコードは、文字列を 2 回実行してコピーするため、非常に非効率的です。

strlen() に入ったら。

それから再び strcpy() で。

また、s1 の NULL をチェックしません。

長さをいくつかの追加の変数に格納することはほとんどコストがかかりませんが、すべての文字列を 2 回実行してコピーすることは大罪です。

于 2009-03-04T13:54:51.917 に答える