utf8 в курсе strncpy - PullRequest
       24

utf8 в курсе strncpy

8 голосов
/ 08 сентября 2011

Мне трудно поверить, что я первый, кто столкнулся с этой проблемой, но долго искал и не нашел решения.

Я хотел бы использовать strncpy, но он должен учитывать UTF8, чтобы он частично не записывал символ utf8 в строку назначения.

В противном случае вы никогда не сможете быть уверены, что полученная строка является допустимой UTF8, даже если вы знаете, что источником является (когда исходная строка больше максимальной длины).

Проверка полученной строки может сработать, но если это нужно будет вызывать много раз, было бы лучше иметь функцию strncpy, которая проверяет ее.

glib имеет g_utf8_strncpy, но при этом копируется определенное количество символов Юникода, тогда как я ищу функцию копирования, которая ограничивается длиной байта.

Чтобы быть понятным, под "осведомленным о utf8" я имею в виду, что он не должен превышать предел целевого буфера и он должен никогда копировать только часть utf-8 персонаж. (При наличии правильного ввода utf-8 никогда не должно быть неправильного вывода utf-8).


Примечание:

В некоторых ответах указывалось, что strncpy обнуляет все байты и что оно не обеспечивает нулевое завершение, в ретроспективе я должен был попросить utf8, осведомленный strlcpy, однако в то время, когда я не делал этого ' не знаю о существовании этой функции.

Ответы [ 6 ]

6 голосов
/ 08 января 2015

Я проверял это на многих образцах строк UTF8 с многобайтовыми символами.Если источник слишком длинный, он выполняет его обратный поиск (начинается с нулевого терминатора) и работает в обратном направлении, чтобы найти последний полный символ UTF8, который может поместиться в целевой буфер.Это всегда гарантирует, что назначение окончено нулем.

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;
}
6 голосов
/ 08 сентября 2011

Я не уверен, что вы имеете в виду под 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, и вам нужно сделать это только один раз.)

1 голос
/ 15 сентября 2011

Чтобы ответить на собственный вопрос, вот функция C, с которой я закончил (я не использовал C ++ для этого проекта):

Примечания: - Поймите, что это не клон strncpy для utf8, это больше похоже на strlcpy от openbsd. - utf8_skip_data скопирована с glib's gutf8.c - Он не проверяет utf8 - именно это я и собирался.

Надеюсь, что это полезно для других и заинтересованных в обратной связи, но, пожалуйста, не допускайте педантичного фанатизма о NULL поведении завершения, если это не фактическая ошибка, или вводящее в заблуждение / неправильное поведение.

Спасибо Джеймсу Канзе, который предоставил основание для этого, но был неполным и 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;
}
1 голос
/ 09 сентября 2011

Вот решение 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 th (сам по себе не является комбинирующим символом), а целевой буфер достаточно велик для хранения кодировки UTF-8символы от 1 до n , но не объединяющие символы символа n .В этом случае записываются байты, представляющие символы от 1 до n , но ни один из символов объединения n не записывается.Фактически можно сказать, что символ n th частично записан.

1 голос
/ 08 сентября 2011

strncpy() ужасная функция:

  1. Если места недостаточно, результирующая строка не будет обнуляться .
  2. Если места достаточно, остаток заполняется значениями NUL. Это может быть болезненно, если целевая строка очень большая.

Даже если символы остаются в диапазоне ASCII (0x7f и ниже), результирующая строка не будет той, которую вы хотите. В случае UTF-8 он может не заканчиваться нулем , а заканчиваться недопустимой последовательностью UTF-8.

Лучший совет - избегать strncpy().

EDIT: Объявление 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;
}
0 голосов
/ 28 октября 2012

Чтобы прокомментировать приведенный выше ответ "strncpy () - ужасная функция:".Я ненавижу даже комментировать такие общие заявления за счет создания еще одного джихада интернет-программирования, но, тем не менее, произойдет, поскольку подобные заявления вводят в заблуждение тех, кто может прийти сюда для поиска ответов.

Хорошо, возможно, строка Cфункции "старой школы".Может быть, все строки в C / C ++ должны быть в каких-то интеллектуальных контейнерах и т. Д., Возможно, следует использовать C ++ вместо C (когда у вас есть выбор), это скорее предпочтение и аргумент для других тем.

Я пришел сюда в поисках UTF-8 strncpy () для себя.Не то чтобы я не смог сделать один (кодировка ИМХО проста и элегантна), но хотел посмотреть, как другие сделали свой и, возможно, найти оптимизированный в ASM.

К «божественному дару» мира программированиялюди, отложите свою гордость на мгновение и посмотрите на некоторые факты.

Нет ничего плохого в "strncpy ()" или любых других подобных функциях с такими же побочными эффектами и проблемами, такими как "_snprintf () "и т. д.

Я говорю:" strncpy () не ужасен ", а скорее" ужасные программисты используют его ужасно ".

Что такое" ужасно "- это не знание правил.Более того, в целом из-за проблем безопасности (таких как переполнение буфера) и стабильности программы, например, Microsoft не нужно было бы добавлять в свою CRT-библиотеку «Safe String Functions», если бы правила были просто соблюдены.

Основные из них:

  1. "sizeof ()" возвращает длину статической строки с терминатором.
  2. "strlen ()" возвращает длину строки без терминатора.
  3. Большинство, если не все функции "n", просто зажимают 'n' без добавления терминатора.
  4. Существует неявная неоднозначность того, что такое «размер буфера» в функциях, которые требуют и размер входного буфера.IE Типы "(char * pszBuffer, int iBufferSize)".Безопаснее предположить худшее и передать размер на единицу меньше, чем фактический размер буфера, и добавить терминатор в конце, чтобы быть уверенным.
  5. Для строковых входов, буферов и т. Д. Установите и используйте разумный предел размерана основе ожидаемого среднего и максимального.Надеюсь, чтобы избежать усечения ввода и устранить период переполнения буфера.

Вот как я лично обращаюсь с такими вещами и другими правилами, которые просто должны быть известны и применены на практике.

Aудобный макрос для статического размера строки:

// 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 () типа вызова обнуления всего буфера. Это делает вещи чище для отладки, хотя я предпочитаю этот стиль для статического (против локального)/ stack) строковые буферы.

Теперь "strncpy ()", следуя правилам:

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

Есть и другие "правила" и проблемы, конечно, но это главноев тех, которые приходят на ум.Вы только что узнали, как работают функции lib, и использовали безопасные методы, подобные этой.

Наконец, в моем проекте я все равно использую ICU , поэтому я решил пойти с ним и использовать макросы в"utf8.h", чтобы сделать мой собственный "strncpy ()".

...