8

私がこの問題に遭遇した最初の人であるとは信じがたいですが、かなりの時間を探して、これに対する解決策を見つけられませんでした。

strncpyを使用したいのですが、UTF8に対応しているため、宛先文字列にutf8文字が部分的に書き込まれません。

そうしないと、ソースが有効であることがわかっていても(ソース文字列が最大長よりも大きい場合)、結果の文字列が有効なUTF8であるかどうかを確認できません。

結果の文字列の検証は機能しますが、これを頻繁に呼び出す場合は、それをチェックするstrncpy関数を使用することをお勧めします。

glibにはありますg_utf8_strncpyが、これは特定の数のUnicode文字をコピーしますが、Imはバイト長によって制限されるコピー関数を探しています。

明確にするために、「utf8対応」とは、宛先バッファーの制限を超えてはならず、utf-8文字の一部のみをコピーしてはならないことを意味します(有効なutf-8入力が与えられた場合、無効なutf-8出力が発生することはありません)。


ノート:

一部の回答では、すべてのバイトがnullであり、ゼロ終了が保証されないことが指摘されていstrncpyます。振り返ってみると、utf8対応を要求する必要がありstrlcpyましたが、当時はこの関数の存在を知りませんでした。

4

6 に答える 6

8

マルチバイト文字を含む多くのサンプルUTF8文字列でこれをテストしました。ソースが長すぎる場合は、ソースの逆検索を実行し(nullターミネータから開始)、逆方向に動作して、宛先バッファに収まる最後の完全なUTF8文字を見つけます。宛先がnullで終了することを常に保証します。

char* utf8cpy(char* dst, const char* src, size_t sizeDest )
{
    if( sizeDest ){
        size_t sizeSrc = strlen(src); // number of bytes not including null
        while( sizeSrc >= sizeDest ){

            const char* lastByte = src + sizeSrc; // Initially, pointing to the null terminator.
            while( lastByte-- > src )
                if((*lastByte & 0xC0) != 0x80) // Found the initial byte of the (potentially) multi-byte character (or found null).
                    break;

            sizeSrc = lastByte - src;
        }
        memcpy(dst, src, sizeSrc);
        dst[sizeSrc] = '\0';
    }
    return dst;
}
于 2015-01-08T04:05:57.883 に答える
7

UTF-8対応とはどういう意味かわかりません。strncpy文字ではなくバイトをコピーし、バッファのサイズもバイト単位で指定されます。完全なUTF-8文字のみをコピーするという意味の場合、たとえば、次の文字のスペースがない場合は停止しますが、そのような機能はわかりませんが、それほど難しくはありません。書く:

int
utf8Size( char ch )
{
    static int const sizeTable[] =
    {
        //  ...
    };
    return sizeTable( static_cast<unsigned char>( ch ) )
}

char*
stru8ncpy( char* dest, char* source, int n )
{
    while ( *source != '\0' && utf8Size( *source ) < n ) {
        n -= utf8Size( *source );
        switch ( utf8Size( ch ) ) {
        case 6:
            *dest ++ = *source ++;
        case 5:
            *dest ++ = *source ++;
        case 4:
            *dest ++ = *source ++;
        case 3:
            *dest ++ = *source ++;
        case 2:
            *dest ++ = *source ++;
        case 1:
            *dest ++ = *source ++;
            break;
        default:
            throw IllegalUTF8();
        }
    }
    *dest = '\0';
    return dest;
}

(utf8Sizeのテーブルの内容を生成するのは少し面倒ですが、これはUTF-8を扱う場合に頻繁に使用する関数であり、一度だけ実行する必要があります。)

于 2011-09-08T08:05:05.847 に答える
2

strncpy()ひどい機能です:

  1. 十分なスペースがない場合、結果の文字列はヌル終了されません
  2. 十分なスペースがある場合、残りはNULで埋められます。ターゲット文字列が非常に大きい場合、これは苦痛になる可能性があります。

文字がASCII範囲(0x7f以下)にとどまっている場合でも、結果の文字列は希望どおりにはなりません。UTF-8の場合、ヌル文字で終了せず、無効なUTF-8シーケンスで終了する可能性があります。

最善のアドバイスは避けることstrncpy()です。

編集: 広告1):

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

int main (void)
{
char buff [4];

strncpy (buff, "hello world!\n", sizeof buff );
printf("%s\n", buff );

return 0;
}

同意すると、バッファはオーバーランしません。しかし、結果はまだ望ましくありません。strncpy()は、問題の一部のみを解決します。それは誤解を招き、望ましくありません。

更新(2012-10-31):これは厄介な問題なので、醜いstrncpy()の動作を模倣して、自分のバージョンをハックすることにしました。ただし、戻り値はコピーされた文字数です。

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

size_t utf8ncpy(char *dst, char *src, size_t todo);
static int cnt_utf8(unsigned ch, size_t len);

static int cnt_utf8(unsigned ch, size_t len)
{
if (!len) return 0;

if ((ch & 0x80) == 0x00) return 1;
else if ((ch & 0xe0) == 0xc0) return 2;
else if ((ch & 0xf0) == 0xe0) return 3;
else if ((ch & 0xf8) == 0xf0) return 4;
else if ((ch & 0xfc) == 0xf8) return 5;
else if ((ch & 0xfe) == 0xfc) return 6;
else return -1; /* Default (Not in the spec) */
}

size_t utf8ncpy(char *dst, char *src, size_t todo)
{
size_t done, idx, chunk, srclen;

srclen = strlen(src);
for(done=idx=0; idx < srclen; idx+=chunk) {
        int ret;
        for (chunk=0; done+chunk < todo; chunk++) {
                ret = cnt_utf8( src[idx+chunk], srclen - (idx+chunk) );
                if (ret ==1) continue;  /* Normal character: collect it into chunk */
                if (ret < 0) continue;  /* Bad stuff: treat as normal char */
                if (ret ==0) break;     /* EOF */
                if (!chunk) chunk = ret;/* an UTF8 multibyte character */
                else ret = 1;           /* we allready collected a number (chunk) of normal characters */
                break;
                }
        if (ret > 1 && done+chunk > todo) break;
        if (done+chunk > todo) chunk = todo - done;
        if (!chunk) break;
        memcpy( dst+done, src+idx, chunk);
        done += chunk;
        if (ret < 1) break;
        }
        /* This is part of the dreaded strncpy() behavior:
        ** pad the destination string with NULs
        ** upto its intended size
        */
if (done < todo) memset(dst+done, 0, todo-done);
return done;
}

int main(void)
{
char *string = "Hell\xc3\xb6 \xf1\x82\x82\x82, world\xc2\xa1!";
char buffer[30];
unsigned result, len;

for (len = sizeof buffer-1; len < sizeof buffer; len -=3) {
        result = utf8ncpy(buffer, string, len);
        /* remove the following line to get the REAL strncpy() behaviour */
        buffer[result] = 0;
        printf("Chop @%u\n", len );
        printf("Org:[%s]\n", string );
        printf("Res:%u\n", result );
        printf("New:[%s]\n", buffer );
        }

return 0;
}
于 2011-09-08T09:46:02.543 に答える
2

自分の質問に答えるために、私が最終的に使用したC関数を次に示します(このプロジェクトではC ++を使用していません)。

注:-これはstrncpyutf8のクローンではなく、 strlcpyopenbsdのクローンに似ていることを認識してください。--glibのgutf8.cからコピーされたutf8_skip_data--utf8を検証しません-これは私が意図したものです。

NULLこれが他の人に役立ち、フィードバックに興味があることを願っていますが、実際のバグ、または誤解を招く/誤った動作でない限り、終了動作についての衒学者の熱心な人はしないでください。

これの基礎を提供してくれたJamesKanzeに感謝しますが、不完全でC ++(Cバージョンが必要です)でした。

static const size_t utf8_skip_data[256] = {
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
    2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
    3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,1,1
};

char *strlcpy_utf8(char *dst, const char *src, size_t maxncpy)
{
    char *dst_r = dst;
    size_t utf8_size;

    if (maxncpy > 0) {
        while (*src != '\0' && (utf8_size = utf8_skip_data[*((unsigned char *)src)]) < maxncpy) {
            maxncpy -= utf8_size;
            switch (utf8_size) {
                case 6: *dst ++ = *src ++;
                case 5: *dst ++ = *src ++;
                case 4: *dst ++ = *src ++;
                case 3: *dst ++ = *src ++;
                case 2: *dst ++ = *src ++;
                case 1: *dst ++ = *src ++;
            }
        }
        *dst= '\0';
    }
    return dst_r;
}
于 2011-09-15T13:58:07.480 に答える
1

C++ソリューションは次のとおりです。

u8string.h

#ifndef U8STRING_H
#define U8STRING_H 1
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif

/**
 * Copies the first few characters of the UTF-8-encoded string pointed to by
 * \p src into \p dest_buf, as many UTF-8-encoded characters as can be written in
 * <code>dest_buf_len - 1</code> bytes or until the NUL terminator of the string
 * pointed to by \p str is reached.
 *
 * The string of bytes that are written into \p dest_buf is NUL terminated
 * if \p dest_buf_len is greater than 0.
 *
 * \returns \p dest_buf
 */
char * u8slbcpy(char *dest_buf, const char *src, size_t dest_buf_len);

#ifdef __cplusplus
}
#endif
#endif

u8slbcpy.cpp

#include "u8string.h"

#include <cstring>
#include <utf8.h>

char * u8slbcpy(char *dest_buf, const char *src, size_t dest_buf_len)
{
    if (dest_buf_len <= 0) {
        return dest_buf;
    } else if (dest_buf_len == 1) {
        dest_buf[0] = '\0';
        return dest_buf;
    }

    size_t num_bytes_remaining = dest_buf_len - 1;
    utf8::unchecked::iterator<const char *> it(src);
    const char * prev_base = src;
    while (*it++ != '\0') {
        const char *base = it.base();
        ptrdiff_t diff = (base - prev_base);
        if (num_bytes_remaining < diff) {
            break;
        }
        num_bytes_remaining -= diff;
        prev_base = base;
    }

    size_t n = dest_buf_len - 1 - num_bytes_remaining;
    std::memmove(dest_buf, src, n);
    dest_buf[n] = '\0';

    return dest_buf;
}

この関数u8slbcpy()にはCインターフェースがありますが、C++で実装されています。私の実装では、ヘッダーのみのUTF8-CPPライブラリを使用しています。

これはほとんどあなたが探しているものだと思いますが、結合文字がn番目の文字(それ自体は結合文字ではない)と宛先バッファーは、文字1からnのUTF-8エンコードを格納するのに十分な大きさですが、文字nの結合文字は格納できません。この場合、文字1からnを表すバイトが書き込まれますが、 nの結合文字は書き込まれません。事実上、n番目の文字は部分的に書かれていると言えます。

于 2011-09-08T22:09:58.490 に答える
0

上記の答えにコメントするには、「strncpy()はひどい関数です:」。私はさらに別のインターネットプログラミングジハードを作成することを犠牲にしてそのような包括的な声明にコメントすることさえ嫌いですが、このような声明は答えを探すためにここに来るかもしれない人々に誤解を与えるのでとにかくそうします。

さて、C文字列関数は「古い学校」かもしれません。たぶん、C / C ++のすべての文字列は、ある種のスマートコンテナなどにあるはずです。おそらく、Cの代わりにC ++を使用する必要があります(選択肢がある場合)。これらは、他のトピックの好みであり、議論です。

私は自分自身のUTF-8strncpy()を探してここに来ました。私はそれを作ることができなかったわけではありませんが(エンコーディングはIMHOのシンプルでエレガントです)、他の人がどのように作ったのかを見て、おそらくASMで最適化されたものを見つけたいと思いました。

プログラミング界の人々の「神々の贈り物」に、あなたの傲慢さを少し脇に置いて、いくつかの事実を見てください。

「strncpy()」、または「_snprintf()」などの同じ副作用と問題を持つ他の同様の関数には何も問題はありません。

私は言います:「strncpy()はひどいものではありません」、むしろ「ひどいプログラマーはそれをひどく使用します」。

「ひどい」とは、ルールを知らないことです。さらに、セキュリティ(バッファオーバーランなど)とプログラムの安定性への影響のために、ルールが守られていれば、たとえばMicrosoftがCRTlibの「SafeStringFunctions」に追加する必要はありません。

主なもの:

  1. 「sizeof()」は、ターミネータ付きの静的文字列の長さを返します。
  2. 「strlen()」は、ターミネータなしの文字列の長さを返します。
  3. ほとんどの場合、すべての「n」関数は、ターミネータを追加せずに「n」にクランプします。
  4. バッファサイズを必要とし、入力する関数の「バッファサイズ」には、暗黙のあいまいさがあります。IE「(char * pszBuffer、int iBufferSize)」タイプ。最悪の事態を想定し、実際のバッファサイズより1小さいサイズを渡し、確実に最後にターミネータを追加する方が安全です。
  5. 文字列入力、バッファなどの場合、予想される平均と最大に基づいて適切なサイズ制限を設定して使用します。うまくいけば、入力の切り捨てを回避し、バッファオーバーラン期間を排除します。

これが私が個人的にそのようなこと、そしてただ知られ実践されるべき他の規則を扱う方法です。

静的文字列サイズの便利なマクロ:

// Size of a string with out terminator
#define SIZESTR(x) (sizeof(x) - 1)

ローカル/スタック文字列バッファを宣言する場合:

A)たとえば、ターミネータのサイズは1023 + 1に制限されており、最大1023文字の文字列を使用できます。

B)文字列の長さをゼロに初期化し、さらに「n」の切り捨ての可能性をカバーするために最後で終了します。

char szBuffer[1024]; szBuffer[0] = szBuffer[SIZESTR(szBuffer)] = 0;

代わりに、次のこともできます。 char szBuffer[1024] = {0}; もちろんですが、コンパイラが生成した「memset()は、バッファ全体をゼロにする呼び出しのように、パフォーマンスに影響します。ただし、デバッグはよりクリーンになります。静的(vs local /スタック)文字列バッファ。

これで、次のルールに従う「strncpy()」が実行されます。

char szBuffer[1024]; szBuffer[0] = szBuffer[SIZESTR(szBuffer)] = 0; 
strncpy(szBuffer, pszSomeInput, SIZESTR(szBuffer));

もちろん、他にも「ルール」や問題がありますが、これらが頭に浮かぶ主なものです。lib関数がどのように機能するかを知り、このような安全な方法を使用するようになりました。

最後に、私のプロジェクトではとにかくICUを使用しているので、ICUを使用して、「utf8.h」のマクロを使用して独自の「strncpy()」を作成することにしました。

于 2012-10-28T15:22:33.727 に答える