Как char[] может представлять строку UTF-8?

В C11 добавлен новый строковый литерал с префиксом u8. Это возвращает массив символов с текстом, закодированным в UTF-8. Как это вообще возможно? Разве обычный символ не подписан? Это означает, что у него на один бит меньше информации для использования из-за знакового бита? Моя логика изображала бы, что строка текста UTF-8 должна быть массивом беззнаковых символов.


person dodehoekspiegel    schedule 11.01.2012    source источник
comment
UTF-8 представляет символы, используя более 8 бит (что меня всегда смущает, поскольку UTF-16 имеет 16 бит). Кроме того, символ — это просто группа битов, поэтому, возможно, имеет значение знак или нет, только если вы думаете о значении как о числе. Если вы думаете об этом как о представлении (части) символа utf-8, не имеет значения, считает ли компилятор, что эта область в памяти представляет число со знаком или без знака. (Это не ответ, просто моя логика интерпретирует это.)   -  person Oliver    schedule 11.01.2012
comment
@Oliver Что вас смущает? UTFf-8 такой же 8-битный, как UTF-16 16-битный.   -  person Mr Lister    schedule 11.01.2012
comment
@MrLister Символы UTF-16 занимают 1 или 2 байта памяти. Символы UTF-8 могут занимать любое количество байтов памяти (обычно от 1 до 6 байт). Так что, на мой взгляд, «UTF-8» будет 8-битной кодировкой, подобной ascii. В то время как настоящий UTF-8 лучше назвать UTF-48 или аналогичный. Или, по крайней мере, я думаю, что это так работает. Я никогда не понимал кодировки символов переменной ширины, когда пару лет назад работал на C, а теперь я работаю с более счастливыми языками, где это на самом деле не имеет значения...   -  person Oliver    schedule 11.01.2012
comment
@Oliver: нет, символы UTF-16 занимают либо 1, либо 2 16-битных блока, так что в типичной реализации C это либо 2, либо 4 байта. n в UTF-n означает размер единицы кода, а не размер символа в битах. Это справедливо для UTF-7, UTF-8, UTF-16 и UTF-32. Все они, кроме UTF-32, используют переменное количество кодовых единиц на кодовую точку Unicode.   -  person Steve Jessop    schedule 11.01.2012
comment
@ Оливер, что он сказал. И UTF-8 максимум 32 бита, а не 48.   -  person Mr Lister    schedule 11.01.2012
comment
Я думаю, что это очень хороший вопрос, поскольку стандарт C использует только unsigned char для байтовых представлений объектов.   -  person u0b34a0f6ae    schedule 12.01.2012


Ответы (4)


Разве обычный символ не подписан?

Это зависит от реализации, является ли char signed или unsigned.

Кроме того, бит знака не «теряется», его все еще можно использовать для представления информации, а char не обязательно имеет размер 8 бит (на некоторых платформах он может быть больше).

person Fred Foo    schedule 11.01.2012
comment
может быть и без подписи ... но не одновременно :-) - person Stephen C; 11.01.2012
comment
Стандарт говорит, что char всегда имеет длину 1 байт. Однако размер байта может варьироваться. Используйте CHAR_BIT (из limits.h), чтобы узнать фактический размер 1 байта. - person jweyrich; 12.01.2012

Здесь есть потенциальная проблема:

Если реализация с CHAR_BIT == 8 использует представление величины знака для char (поэтому char является знаковым), то когда UTF-8 требует битового шаблона 10000000, это отрицательный 0. Таким образом, if реализация также не поддерживает отрицательный 0, тогда заданная строка UTF-8 может содержать недопустимое (ловушку) значение char, что проблематично. Даже если он поддерживает отрицательный нуль, тот факт, что битовый шаблон 10000000 сравнивается равным как char с битовым шаблоном 00000000 (нулевой терминатор), может вызвать проблемы при использовании данных UTF-8 в char[].

Я думаю, это означает, что для реализации C11 со знаком величины char должно быть беззнаковым. Обычно от реализации зависит, является ли char подписанным или неподписанным, но, конечно, если char подписанное приводит к невозможности правильной реализации литералов UTF-8, тогда разработчик просто должен выбрать неподписанный. Кроме того, это всегда имело место для реализаций C++ с дополнением, отличным от 2, поскольку C++ позволяет использовать char, а также unsigned char для доступа к представлениям объектов. C позволяет только unsigned char.

В дополнении до 2 и дополнении до 1 битовые шаблоны, необходимые для данных UTF-8, являются допустимыми значениями signed char, поэтому реализация может сделать char либо знаковым, либо беззнаковым, и по-прежнему иметь возможность представлять строки UTF-8 в char[]. Это связано с тем, что все 256-битные шаблоны являются допустимыми значениями дополнения 2, а UTF-8 не использует байт 11111111 (отрицательный нуль дополнения 1).

person Steve Jessop    schedule 11.01.2012
comment
В вашем сообщении используется неверная предпосылка, а именно, что реализации были бы достаточно глупыми, чтобы допускать значения -0 для символов. Они никогда не бывают. - person Mr Lister; 11.01.2012
comment
@Mr Lister: я не думаю, что мой ответ делает какое-либо предположение о том, что на самом деле делают реализации. Он просто перечисляет то, что им (не) разрешено делать, и, в частности, одно представление, недавно исключенное требованием в C11 для поддержки UTF-8. Для всех практических целей каждая реализация является дополнением 2, но стандарт продолжает разрешать (глупые) альтернативы. - person Steve Jessop; 11.01.2012
comment
Я думаю, что ваш пост очень проницательный, но вот где я запутался: стандарт C++11 позволяет использовать unsigned char и char для псевдонимов (см. §3.10/15), а C11 даже позволяет все типы символов (см. §6.5/7). Для меня это означает, что эти типы должны быть способны читать байт со значением 11111111 (или любое другое значение байта). В C++11 это можно решить, создав простой char unsigned если дополнение 2 не используется. Но в C11 это никогда не может быть решено, если дополнение 2 не используется, потому что сглаживание должно работать со всеми типами символов (§6.5/7), то есть даже явно... - person JohnCand; 13.11.2013
comment
объявленный подписанный char. Это означает, что C11 неявно предписывает дополнение до 2 (чтобы не было значений-ловушек), в то же время допуская дополнение до 1 и величину знака в §6.2.6.2/2. Я думаю, что это ошибка в стандарте. Что вы думаете? Мое мнение таково, что и C++, и C должны предписывать дополнение 2 и остановить это, мы будем поддерживать все, чего бы это ни стоило, до конца времен, беспорядок, который серьезно сбивает с толку тех, кто хочет писать код, соответствующий стандарту. Если все еще используется значимый процессор, который не использует дополнение 2, можно легко использовать... - person JohnCand; 13.11.2013
comment
... флаг компилятора, выбирающий более старый стандарт C или C++, который это позволяет. - person JohnCand; 13.11.2013
comment
@JohnCaC2: у меня нет под рукой стандартов, но я думаю, что если все типы символов разрешены для псевдонимов, это не означает, что вы должны использовать дополнение до 2. Это просто означает, что вы должны поддерживать отрицательный нуль для типов char, если вы не используете дополнение 2. Это позволяет избежать значений-ловушек, хотя есть побитовые значения, которые при сравнении равны. Последнее, хотя это было бы ошибкой реализации при обработке данных UTF-8 с использованием величины знака, как я описываю в ответе, не является ошибкой реализации для псевдонимов. - person Steve Jessop; 13.11.2013

Нет, бит знака все же немного! И сама спецификация UTF-8 не говорит, что символы должны быть без знака.

PS Что такое kookwekker voor 'n naam?

person Mr Lister    schedule 11.01.2012

Подпись char не имеет значения; utf8 можно обрабатывать только с помощью операций сдвига и маски (что может быть громоздко для подписанных типов, но не невозможно).

Для иллюстрации по пунктам: следующие фрагменты не содержат арифметических операций над значением символа, только сдвиг и маска.

static int eat_utf8(unsigned char *str, unsigned len, unsigned *target)
{
unsigned val = 0;
unsigned todo;

if (!len) return 0;

val = str[0];
if ((val & 0x80) == 0x00) { if (target) *target = val; return 1; }
else if ((val & 0xe0) == 0xc0) { val &= 0x1f; todo = 1; }
else if ((val & 0xf0) == 0xe0) { val &= 0x0f; todo = 2; }
else if ((val & 0xf8) == 0xf0) { val &= 0x07; todo = 3; }
else if ((val & 0xfc) == 0xf8) { val &= 0x03; todo = 4; }
else if ((val & 0xfe) == 0xfc) { val &= 0x01; todo = 5; }
else {  /* Default (Not in the spec) */
        if (target) *target = val;
        return -1; }


len--;str++;
if (todo > len) { return -todo; }

for(len=todo;todo--;) {
        /* For validity checking we should also
        ** test if ((*str & 0xc0) == 0x80) here */
        val <<= 6;
        val |= *str++ & 0x3f;
        }

if (target) *target = val;
return  1+ len;
}
person wildplasser    schedule 11.01.2012
comment
Обратите внимание, что Стандарт гарантирует CHAR_BIT ≥ 8. - person J. C. Salomon; 31.01.2012