web-dev-qa-db-ja.com

strncpy()の最良の代替手段は何ですか?

関数strncpy()は常にnullで終了するとは限らないので、常にnullで終了する最良の代替方法を知りたいですか?次のような関数が必要です。

strlen(src) >= n /*n is the number of characters to be copied from source*/

次のようなコードをさらに追加する必要はありません。

buf[sizeof(buf)-1] = 0; 
6
Demetrio

コピーする文字列の長さが不明な場合は、ここで snprintf を使用できます。この関数は、フォーマットされた出力をstrに送信します。 sprintf()と同様に動作しますが、代わりにstrによって割り当てられたバイトをそれ以上書き込みません。結果の文字列が_n-1_文字より長い場合、残りの文字は省略されます。また、バッファサイズが_\0_でない限り、常にnullターミネータ_0_が含まれます。

これは、本当に使用したくない場合は、strncpy()またはstrcpy()の代わりになります。ただし、strcpy()を使用して文字列の最後にnullターミネータを手動で追加することは、常に単純で効率的なアプローチです。 Cでは、処理された文字列の最後にnullターミネータを追加するのはごく普通のことです。

sprintf()の基本的な使用例を次に示します。

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

#define SIZE 1024

int main(void) {
    const size_t N = SIZE;
    char str[N];
    const char *example = "Hello World";

    snprintf(str, sizeof(str), "%s", example);

    printf("String = %s, Length = %zu\n", str, strlen(str));

    return 0;
}
_

印刷するもの:

_String = Hello World, Length = 11
_

この例は、snprintf()が_"Hello World"_をstrにコピーし、最後に_\0_ターミネータを追加したことを示しています。

注:strlen()はnullで終了する文字列でのみ機能し、文字列が次の場合は 未定義の動作 nullで終了していません。 snprintf()には、さらにエラーチェックが必要です。これは manページ にあります。

他の人が言っているように、これは効率的なアプローチではありませんが、見に行くとそこにあります。

6
RoadRunner

必要な動作が、ソース文字列の最長の初期プレフィックスを既知のサイズのバッファにコピーするstrcpyの切り捨てバージョンである場合、複数のオプションがあります。

  • あなたは仕事をするオーダーメイドの関数を書くことができます:

    _char *safe_strcpy(char *dest, size_t size, char *src) {
        if (size > 0) {
            size_t i;
            for (i = 0; i < size - 1 && src[i]; i++) {
                 dest[i] = src[i];
            }
            dest[i] = '\0';
        }
        return dest;
    }
    _

    ほとんどのBSDシステムには、同じように動作する関数strlcpy(char *dest, const char *src, size_t n);があります。 ndest配列のサイズですが、src引数の後に続くため、引数の順序はわかりにくいです。

  • strncat()を使用できます:

    _char *safe_strcpy(char *dest, size_t size, char *src) {
        if (size > 0) {
            *dest = '\0';
            strncat(dest, src, size - 1);
        }
        return dest;
    }
    _
  • snprintf()またはsprintf()を使用できますが、油圧プレスを使用して釘を打ち込むような感覚です。

    _snprintf(dest, size, "%s", src);
    _

    代わりに:

    _if (size > 0) {
        sprintf(dest, "%.*s", (int)(size - 1), src);
    }
    _
  • strlen()およびmemcpy()を使用できますが、これは、ソースptrがnullで終了する文字列を指していることがわかっている場合にのみ可能です。また、ソース文字列が宛先配列よりもはるかに長い場合は、上記の両方のソリューションよりも効率が低くなります。

    _char *safe_strcpy(char *dest, size_t size, char *src) {
        if (size > 0) {
            size_t len = strlen(src);
            if (len >= size)
                len = size - 1;
            memcpy(dest, src, len);
            dest[len] = '\0';
        }
        return dest;
    }
    _

    ターゲットシステムで利用可能な場合は、strnlen()を使用して非効率を回避できます。

    _char *safe_strcpy(char *dest, size_t size, char *src) {
        if (size > 0) {
            size_t len = strnlen(src, size - 1);
            memcpy(dest, src, len);
            dest[len] = '\0';
        }
        return dest;
    }
    _
  • strncpy()を使用して、null終了を強制することができます。 strncpy()もまた、ソース文字列が短い場合、宛先配列の残りの部分を埋めるため、宛先配列が大きい場合、これは非効率的です。この関数のセマンティクスは非常に直感に反し、理解が不十分で、エラーが発生しやすくなっています。正しく使用されている場合でも、strncpy()の発生は、発生するのを待っているバグです。次のプログラマーは、大胆でありながら知識が少ないため、コードを変更して導入しようとする可能性があります最適化彼が理解していないコード。安全にプレイしてください:この機能は避けてください。

この質問のもう1つの側面は、呼び出し元が切り捨てを検出する機能です。上記の_safe_strcpy_の実装は、strcpyと同様にターゲットポインタを返すため、呼び出し元に情報を提供しません。 snprintf()は、ターゲット配列が十分に大きい場合にコピーされたであろう文字数を表すintを返します。この場合、戻り値はstrlen(src)に変換されます。 int。これにより、呼び出し元は切り捨てやその他のエラーを検出できます。

さまざまな部分から文字列を作成するのに適した別の関数を次に示します。

_size_t strcpy_at(char *dest, size_t size, size_t pos, const char *src) {
    size_t len = strlen(src);
    if (pos < size) {
        size_t chunk = size - pos - 1;
        if (chunk > len)
            chunk = len;
        memcpy(dest + pos, src, chunk);
        dest[pos + chunk] = '\0';
    }
    return pos + len;
}
_

この関数は、未定義の動作なしでシーケンスで使用できます。

_void say_hello(const char **names, size_t count) {
    char buf[BUFSIZ];
    char *p = buf;
    size_t size = sizeof buf;

    for (;;) {
        size_t pos = strcpy_at(p, size, 0, "Hello");
        for (size_t i = 0; i < count; i++) {
            pos = strcpy_at(p, size, pos, " ");
            pos = strcpy_at(p, size, pos, names[i]);
        }
        pos = strcpy_at(p, size, pos, "!");
        if (pos >= size && p == buf) {
            // allocate a larger buffer if required
            p = malloc(size = pos + 1);
            if (p != NULL)
                continue;
            p = buf;
        }
        printf("%s\n", p);
        if (p != buf)
            free(p);
        break;
    }
}
_

snprintfの同等のアプローチも役立ち、アドレスでposを渡します。

_size_t snprintf(char *s, size_t n, size_t *ppos, const char *format, ...) {
    va_list arg;
    int ret;
    size_t pos = *ppos;

    if (pos < n) {
        s += pos;
        n -= pos;
    } else {
        s = NULL;
        n = 0;
    }
    va_start(arg, format);
    ret = snprintf(s, n, format, arg);
    va_end(arg);

    if (ret >= 0)
        *ppos += ret;

    return ret;
}
_

値ではなくアドレスでposを渡すと、_snprintf_at_がsnprintfの戻り値を返すことができます。これは、エンコードエラーの場合は_-1_になります。

answer の代わりにsnprintf()を提案しました:(注:n <= 0の場合のトラブル)

size_t sz = sizeof buf;
/*n is the number of characters to be copied from source*/
int n = (int) sz - 1;
snprintf(buf, sz, "%s", src);

コードは次のprecisionを使用できます。

"... s変換のために書き込まれる最大バイト数。.."C11§7.21.6.14

sprintf(buf, "%.*s", n, src);

src文字列である必要はなく、文字の配列である必要があるという微妙な利点があります。

文字列用の別のツール。

strncpy()の代替を使用する必要があることの例として、strcat()などのシステムAPI関数を誤用するのは簡単すぎることがわかったGit 2.19(2018年第3四半期)を考えてみてください。 strncpy(); ...これらの選択された関数は、このコードベースでは禁止されており、コンパイルの失敗を引き起こします。

そのパッチにはいくつかの選択肢がリストされているため、この質問に関連しています。

commit e488b7acommit cc8fdaecommit 1b11b64 (24 Jul 2018)、および commit c8af66a (26 Jul2018)を参照してください。 )by Jeff King(peff
(合併 Junio C Hamano --gitster- in commit e28daf2 、2018年8月15日)

_banned.h_:strncpy()を禁止としてマークします

strncpy()関数はstrcpy()よりも恐ろしいものではありませんが、終了セマンティクスがおかしいため、誤用されやすいです。
つまり、切り捨てるとNULターミネータが省略されるため、自分で追加することを忘れないでください。正しく使用したとしても、読者がコードを調べずにこれを確認するのは難しい場合があります。
使用を検討している場合は、代わりに次のことを検討してください。

  • strlcpy()本当に切り捨てられているが、NULで終了している文字列が必要な場合(互換バージョンを提供しているため、いつでも利用できます)
  • xsnprintf()コピーするものが適合すると確信している場合
  • 任意の長さのヒープ割り当て文字列を処理する必要がある場合は、strbufまたはxstrfmt()

_compat/regex/regcomp.c_にはstrncpyのインスタンスが1つあることに注意してください。これは問題ありません(コピーする前に十分な大きさの文字列を割り当てます)。
しかし、これは_NO_REGEX=1_でコンパイルする場合でも、禁止リストをトリガーしません。理由は次のとおりです。

  1. コンパイル時にgit-compat-util.hを使用しません(代わりに、アップストリームライブラリのシステムインクルードに依存します)。そして
  2. 「_#ifdef DEBUG_」ブロックにあります

_banned.h_コードはトリガーされないため、アップストリームからの分岐を最小限に抑えるために、そのままにしておくことをお勧めします。


注:1年以上後、Git 2.21(2019年第1四半期)では、「strncat()」関数自体もになりました。禁止された機能。

commit ace5707 (02 Jan 2019)by Eric Wong(_ele828_) を参照してください。
(Merged by Junio C Hamano --gitster- in commit 81bf66b 、18 Jan 2019)

_banned.h_:strncat()を禁止としてマークします

strncat()strcat()と同じ二次動作を持ち、読みにくく、バグが発生しやすくなっています。 Git iselfではまだ問題になっていませんが、strncat()masterの 'cgit'に侵入していることを発見し、システムでセグメンテーション違反を引き起こしました。


Git 2.24(2019年第4四半期)では、「vsprintf」ではなく、「sprintf」の明示的な形式をそれ自体の禁止バージョンとして使用します。

commit 60d198d (25 Aug 2019)by Taylor Blau(ttaylorr を参照してください。
(合併 濱野純雄--gitster- in commit 37801f 、2019年9月30日)

3
VonC

strlcpy()関数を使用します。 strlcpy()宛先バッファのフルサイズを取得し、余裕がある場合はNULL終了を保証します。詳細については、 man ページを参照してください。

2
msc

strcpy関数は常にnullで終了します。もちろん、バッファオーバーフローを防ぐためのコードを含める必要があります。例:

char buf[50];

if (strlen(src) >= sizeof buf)
{
    // do something else...
}
else
    strcpy(buf, src);
0
M.M