Cのstrcpy()
関数は悪いか悪いと思う人もいるようです。バッファオーバーフローを回避するために通常は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()
を使用するのかを知りたいだけです。 (最初の2つのバッファチェックを無視して)3つよりも1つを優先するロジックはありますか?
memcpy
はstrcpy
およびstrncpy
よりも高速である可能性があります。これは、コピーされた各バイトを「\ 0」と比較する必要がなく、コピーされたオブジェクトの長さがすでにわかっているためです。 。 Duff's device を使用して同様の方法で実装するか、movswやmovsdのように一度に数バイトをコピーするアセンブラ命令を使用することができます。
ここ のルールに従っています。それから引用させてください
strncpy
は、ディレクトリエントリなどの構造体の固定長の名前フィールドを処理するために、最初にCライブラリに導入されました。このようなフィールドは、文字列と同じようには使用されません。最大長のフィールドには末尾のnullは不要であり、短い名前の末尾のバイトをnullに設定すると、フィールドごとの効率的な比較が保証されます。 strncpyは、Originによる「有界strcpy」ではなく、委員会は、そのような用途により適した機能に変更するのではなく、既存の慣行を認識することを好みました。
そのため、これまでソース文字列から'\0'
が見つからなかった場合、n
を押しても、文字列の末尾に'\0'
は表示されません。それを誤用するのは簡単です(もちろん、その落とし穴を知っていれば、それを避けることができます)。引用が言うように、それは有界strcpyとして設計されていませんでした。そして、私は必要がなければそれを使用したくないです。あなたの場合、明らかにその使用は必要ではなく、あなたはそれを証明しました。なぜそれを使用するのですか?
そして一般的に言って、プログラミングコードは冗長性を減らすことでもあります。 'n'文字を含む文字列があることがわかっている場合、最大のn
文字をコピーするようにコピー関数に指示するのはなぜですか?冗長チェックを行います。パフォーマンスについてはほとんどありませんが、一貫性のあるコードについてははるかに重要です。読者は、strcpy
がn
文字と交差する可能性があり、コピーを制限する必要があるため、これをマニュアルで読むために何ができるかを自問します。その場合は発生しません。そして、コードの読者の間で混乱が始まります。
mem-
、str-
、またはstrn-
を使用する理由として、上記のリンクされたドキュメントのように、それらの中から選択しました。
mem-
構造体のバイトのように、生のバイトをコピーしたい場合。
str-
ヌル終了文字列をコピーする場合-100%オーバーフローが発生しない場合のみ。
strn-
ある長さまでヌル終了文字列をコピーし、残りのバイトをゼロで埋める場合。おそらく私が望むものではないでしょう。末尾のゼロフィルで事実を忘れがちですが、上記の引用で説明されているように、これは仕様によるものです。したがって、文字をコピーする独自の小さなループをコーディングし、末尾に'\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-
およびwcsn-
(C99
の場合)と呼ばれるワイド文字を操作するための関数を取得しました。私も同じように使います。
人々がstrcpyではなくstrncpyを使用する理由は、文字列が常にnullで終了するわけではなく、バッファ(strcpyで文字列に割り当てたスペース)をオーバーフローさせ、無関係なメモリビットを上書きするのが非常に簡単だからです。
Strcpyではこれはcan発生し、strncpyではこれはnever発生します。そのため、strcpyは安全でないと見なされます。悪は少し強いかもしれません。
率直に言って、Cで多くの文字列処理を行っている場合は、strcpy
またはstrncpy
またはmemcpy
を使用する必要があるかどうかを自問するべきではありません。より高いレベルの抽象化を提供する文字列ライブラリを見つけるか、作成する必要があります。たとえば、各文字列の長さを追跡し、メモリを割り当て、必要なすべての文字列操作を提供するものです。
これにより、バッファオーバーフロー、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ファイルを参照してください。それは私たちの生活をはるかに簡単にしました。一方、このようなライブラリは非常に簡単に作成できるため、演習としてだけでも、自分で作成することができます。
誰も言及していません strlcpy
、 Todd C.MillerとTheode Raadtによって開発されました 。彼らが彼らの論文で言うように:
最も一般的な誤解は、
strncpy()
NUL-宛先文字列を終了するというものです。ただし、これは、ソース文字列の長さがサイズパラメータよりも短い場合にのみ当てはまります。これは、任意の長さのユーザー入力を固定サイズのバッファーにコピーするときに問題になる可能性があります。この状況でstrncpy()
を使用する最も安全な方法は、宛先文字列のサイズより1つ小さい値を渡してから、手動で文字列を終了することです。そうすれば、常にNULで終了する宛先文字列を持つことが保証されます。
strlcpy
の使用には反論があります。ウィキペディアのページには、
Drepperは、
strlcpy
とstrlcat
を使用すると、プログラマーが切り捨てエラーを無視しやすくなり、削除するよりも多くのバグが発生する可能性があると主張しています。 *
ただし、これは、strncpy
への引数の手動調整に加えて、手動のNULL終了を追加するために何をしているのかを知っている人々を強制するだけだと思います。 strlcpy
を使用すると、バッファをNULLで終了できなかったため、バッファオーバーランを回避するのがはるかに簡単になります。
また、glibcまたはMicrosoftのライブラリにstrlcpy
がないことは、使用の障壁にはならないことに注意してください。 strlcpy
とその仲間のソースは、どのBSDディストリビューションでも見つけることができ、ライセンスは、商用/非商用プロジェクトに適している可能性があります。 strlcpy.c
の上部にあるコメントを参照してください。
私は個人的に、コードが有効であることが証明できれば、そして非常に迅速に実行できれば、完全に受け入れられると考えています。つまり、コードが単純で明らかに正しい場合は問題ありません。
ただし、関数の実行中は、他のスレッドがs1
が指す文字列を変更しないと想定しているようです。メモリ割り当てが成功した後(したがってstrlen
の呼び出し)にこの関数が中断され、文字列が大きくなり、bamstrcpy
がNULLバイトにコピーされるため、バッファオーバーフロー状態になるとどうなりますか。
以下の方が良いかもしれません:
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;
}
今、ストリングはあなた自身の過失なしで成長することができ、あなたは安全です。結果は重複することはありませんが、クレイジーなオーバーフローになることもありません。
あなたが提供したコード実際にがバグである確率はかなり低いです(あなたが働いているなら、存在しないとは言わないまでも、存在しないにかなり近いです)スレッド化をまったくサポートしていない環境で)。それはただ考えるべきことです。
[〜#〜] 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
バイトで終了します。どちらの方法も、私が最初にすばやくまとめた方法よりも優れています。
Strncpyも悪だと思います。
この種のプログラミングエラーから自分自身を本当に保護するには、(a)正常に見え、(b)バッファをオーバーランするコードを記述できないようにする必要があります。
これは、バッファと容量を不透明に格納し、それらを永久にバインドし、境界をチェックする実際の文字列抽象化が必要であることを意味します。そうしないと、ストリングとその容量をショップ全体に渡すことになります。文字列の途中を変更するなど、実際の文字列操作に到達すると、宛先が小さすぎるstrcpyを呼び出すのと同じように、間違った長さをstrncpy(特にstrncat)に渡すのはほぼ同じです。
もちろん、その抽象化を実装する際にstrncpyとstrcpyのどちらを使用するかを尋ねる場合もあります。strncpyは、その機能を完全に理解していれば、より安全です。しかし、文字列処理アプリケーションコードでは、バッファオーバーフローを防ぐために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つになる可能性があります。
同意する。ただし、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()
はそれらを解釈し、ワームの缶の棚全体を開くため、これは重要です。
すでに長さを計算している場合は、memcpy
を使用する傾向がありますが、strcpy
は通常、機械語で機能するように最適化されていますが、ライブラリにできるだけ多くの情報を提供する必要があると感じています。できるので、最適なコピーメカニズムを使用できます。
しかし、あなたが与える例では、それは問題ではありません-それが失敗する場合、それは最初のstrlen
にあるので、strncpyは安全性の観点からあなたに何も購入しません(そしておそらくstrncpy
は、境界とnulの両方をチェックする必要があるため、速度が遅くなります。また、memcpy
とstrcpy
の違いは、投機的にコードを変更する価値がありません。
人々がこのようにそれを使用すると、悪が起こります(以下は非常に単純化されていますが):
void BadFunction(char *input)
{
char buffer[1024]; //surely this will **always** be enough
strcpy(buffer, input);
...
}
これは、驚くほど頻繁に発生する状況です。
しかし、ええ、strcpyは、宛先バッファーにメモリーを割り当てていて、すでにstrlenを使用して長さを見つけている状況では、strncpyと同じくらい優れています。
strlenは、最後のnull終了場所までを見つけます。
しかし実際には、バッファはnullで終了しません。
そのため、人々はさまざまな機能を使用します。
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;
}
あなたが説明する状況では、strcpyは良い選択です。このstrdupは、s1が「\ 0」で終わっていない場合にのみ問題になります。
Strcpyに問題がない理由を示すコメントを追加して、他の人(そして1年後の自分自身)がその正しさについてあまりにも長い間疑問に思うのを防ぎます。
strncpyは安全に見えることがよくありますが、問題が発生する可能性があります。ソースの「文字列」がカウントより短い場合は、カウントに達するまでターゲットに「\ 0」を埋め込みます。それはパフォーマンスに悪いかもしれません。ソース文字列がcountより長い場合、strncpyはターゲットに「\ 0」を追加しません。これは、後で「\ 0」で終了する「文字列」を予期したときに問題が発生する可能性があります。したがって、strncpyも注意して使用する必要があります。
'\ 0'で終了する文字列を使用していない場合にのみ、memcpyを使用しますが、それは好みの問題のようです。
char *strdup(const char *s1)
{
char *s2 = malloc(strlen(s1)+1);
if(s2 == NULL)
{
return NULL;
}
strcpy(s2, s1);
return s2;
}
問題:
おそらく他の問題があります...見て、ヌル終了は必ずしも悪い考えではありません。計算効率のため、またはストレージ要件を減らすために、それが理にかなっている状況があります。
汎用コードを書くために、例えばビジネスロジックは理にかなっていますか?番号。
この回答では、_size_t
_とmemcpy()
を使用して、高速で単純なstrdup()
を実現しています。
タイプ_size_t
_を使用するのが最適です。これは、strlen()
から返され、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.11「A stringは、最初のヌル文字で終了し、それを含む文字の連続したシーケンスです。..」
ええと、strcpy()はstrdup()ほど邪悪ではありません-少なくともstrcpy()は標準Cの一部です。