Как Microsoft обрабатывает тот факт, что UTF-16 является кодировкой переменной длины в их реализации стандартной библиотеки C++

Кодирование переменной длины косвенно запрещено стандартом.

Итак, у меня есть несколько вопросов:

Как обрабатывается следующая часть стандарта?

17.3.2.1.3.3 Последовательности расширенных символов

Последовательность расширенных символов — это объект массива (8.3.4) A, который может быть объявлен как TA[N], где T — это тип wchar_t (3.9.1), опционально уточняемый любой комбинацией const или volatile. Начальные элементы массива имеют определенное содержимое вплоть до элемента, определяемого некоторым предикатом включительно. Последовательность символов может быть обозначена значением указателя S, обозначающим ее первый элемент.

Длина NTWCS — это количество элементов, предшествующих завершающему широкому символу null. Пустой NTWCS имеет нулевую длину.

Вопросы:

basic_string<wchar_t>

  • How is operator[] implemented and what does it return?
    • standard: If pos < size(), returns data()[pos]. Otherwise, if pos == size(), the const version returns charT(). Otherwise, the behavior is undefined.
  • Does size() return the number of elements or the length of the string?
    • standard: Returns: a count of the number of char-like objects currently in the string.
  • How does resize() work?
    • unrelated to standard, just what does it do
  • Как обрабатываются позиции в insert(), erase() и других?

cwctype

  • Здесь почти все. Как обрабатывается переменная кодировка?

cwchar

  • getwchar() очевидно, что нельзя вернуть всего персонажа платформы, так как же это работает?

Плюс все остальные функции персонажа (тема та же).

Изменить: я открою награду, чтобы получить подтверждение. Я хочу получить какие-то четкие ответы или хотя бы более четкое распределение голосов.

Изменить: это начинает становиться бессмысленным. Это полно совершенно противоречивых ответов. Некоторые из вас говорят о внешних кодировках (мне все равно, кодировка UTF-8 все равно будет храниться как UTF-16 после чтения в строку, то же самое для вывода), остальные просто противоречат друг другу. :-/


person Šimon Tóth    schedule 26.10.2010    source источник
comment
Возможно, вы могли бы уточнить, что вы подразумеваете под тем фактом, что их поддержка UTF-16 представляет собой кодировку переменной длины (например, пример, когда строка кодируется более чем в 2 байта на символ). Насколько мне известно, UTF-16 рассматривается как кодировка фиксированной длины в документах/коде MS, но я не эксперт, поэтому пример, вероятно, поможет.   -  person Nick    schedule 26.10.2010
comment
Кодирование переменной длины ни в коем случае не запрещено. wstring действительно отслеживает количество элементов, как вы сказали, которые он должен знать для целей распределения, и полностью не зависит от кодировки. Но элементы != количество кодовых точек != количество глифов, вы должны отслеживать другие меры длины.   -  person Ben Voigt    schedule 26.10.2010
comment
@Nick Это интересно, где ты это прочитал?   -  person Šimon Tóth    schedule 26.10.2010
comment
@Let_Me_Be: Может быть, вы смешиваете понятия. Где в стандарте вы видели, что * длина последовательности wchar_t определяется как количество элементов, предшествующих '\0'? И string, и wstring поддерживают строки, содержащие нулевые символы.   -  person David Rodríguez - dribeas    schedule 26.10.2010
comment
@ Дэвид Я говорю только о wstring во второй части вопроса. Первая часть простая wchar_t*. Или wchar_t s[constant];, если быть точным.   -  person Šimon Tóth    schedule 26.10.2010
comment
@Ben: я думаю, что кодирование переменной длины более или менее запрещено для широких строк (это стиль C). Стандарт определяет байтовые строки (ntbs), многобайтовые строки (ntmbs) и широкие строки (ntwcs). Он не определяет многосимвольные широкие строки. Но он также не определяет, как специфичные для платформы API, такие как средство визуализации шрифтов, обрабатывают широкие строки, поэтому я полностью согласен с вами в том, что количество глифов — это совершенно независимая вещь. По сути, wstring::size должен возвращать число wchar_t в строке. Если MS, между прочим, говорит, что на самом деле это UTF-16, а не UCS-2, это не имеет значения.   -  person Steve Jessop    schedule 27.10.2010
comment
@Steve Итак, что возвращает wcslen? Как вы на самом деле определяете длину строки в Windows?   -  person Šimon Tóth    schedule 27.10.2010
comment
wcslen возвращает число wchar_t перед 0. Поддержка Windows для обработки суррогатных пар, AFAIK, мусор. Вам, вероятно, лучше с UTF-8 и _mbslen.   -  person Steve Jessop    schedule 27.10.2010
comment
Я думаю, что этот вопрос как бы запутывает проблему (возможно, нужно отредактировать заголовок/тег). Обработка строк wchar_t в STL не обрабатывает кодировки переменной длины для UTF-16, она их игнорирует (как говорили другие люди). Однако это не проблема Microsoft или проблема с реализацией STL, которую они распространяют: STL (и C++, если уж на то пошло) не имеет никакой обработки кодированных строк переменной длины (UTF-16 или иначе). Вы спрашиваете, какие Windows API доступны для этого, или речь идет только о STL?   -  person Nick    schedule 27.10.2010
comment
@Nick Ну, это проблема Microsoft, потому что, если STL не может обрабатывать кодирование переменной длины, то для поддержки STL у вас не может быть кодирования переменной. Это довольно просто. Вопрос касается C++ (в основном STL), но также и wchar_t. Windows API для меня неактуален (то же самое касается внешних кодировок).   -  person Šimon Tóth    schedule 27.10.2010


Ответы (5)


Вот как реализация Microsoft STL обрабатывает кодировку переменной длины:

basic_string<wchar_t>::operator[])( может возвращать низкий или высокий суррогат по отдельности.

basic_string<wchar_t>::size() возвращает количество wchar_t объектов. Суррогатная пара (один символ Unicode) использует два wchar_t и, следовательно, добавляет два к размеру.

basic_string<wchar_t>::resize() может обрезать строку в середине суррогатной пары.

basic_string<wchar_t>::insert() можно вставить в середину суррогатной пары.

basic_string<wchar_t>::erase() может стереть любую половину суррогатной пары.

В общем, шаблон должен быть ясен: STL не предполагает, что std::wstring находится в UTF-16, и не требует, чтобы он оставался UTF-16.

person MSalters    schedule 27.10.2010
comment
Как определить длину строки, если size() возвращает количество объектов wchar_t? Как вы безопасно вставляете (), стираете () и т. д.? Часть C стандартной библиотеки не поддерживается в Windows для широких символов? - person Šimon Tóth; 27.10.2010
comment
@Let_Me_Be: Что бы вы хотели сделать с этой длиной строки? Что касается безопасной вставки, то в любом случае вы должны найти правильное место для вставки текста. Существуют не только правила UTF-16, но и (человеческие) языки добавляют правила орфографии и грамматики, которые необходимо соблюдать. - person MSalters; 28.10.2010
comment
@Let_Me_Be: Если принять во внимание наличие комбинированных меток в Юникоде, можно понять только то, что весь Юникод на самом деле является кодировкой переменной длины. Что вы ожидаете, если у вас есть строка с кодовыми точками Unicode U+0041 U+0308 (ЛАТИНСКАЯ ЗАГЛАВНАЯ БУКВА A и COMBINING DIARESIS) и вы пытаетесь вставить между ними кодовую точку U+0030 (ЦИФРА НОЛЬ)? Все эти кодовые точки могут быть представлены одним элементом в UTF-16 и UCS-4. - person Bart van Ingen Schenau; 29.10.2010
comment
@Bart Вы говорите о внешней кодировке, которая для меня не имеет значения. Я говорю только о внутренней кодировке. - person Šimon Tóth; 29.10.2010
comment
@Let_Me_Be: Нет, я говорю о внутреннем кодировании. Даже если ваша внутренняя кодировка UCS-4, она остается кодировкой переменной длины из-за наличия объединяющих меток в Unicode. Просто невозможно закодировать каждый символ (как определил бы это непрограммист) в одной кодовой точке. - person Bart van Ingen Schenau; 29.10.2010
comment
@Bart Нет, вы используете слово символ во внешнем значении представленный символ. - person Šimon Tóth; 29.10.2010
comment
@Let_Me_Be: Тогда, пожалуйста, просветите меня. Каким будет внутреннее представление символа, который внешне виден как буква А с чертой над ней (ЛАТИНСКАЯ ЗАГЛАВНАЯ А с КОМБИНИРОВАННОЙ ВЕРХНЕЙ ЧЕРНИЦЕЙ)? - person Bart van Ingen Schenau; 29.10.2010
comment
@Let_Me_Be: И какая разница для пользователя программы для работы с текстом? Для пользователя он выглядит как один глиф, поэтому программа должна рассматривать его как один символ. - person Bart van Ingen Schenau; 29.10.2010
comment
@Bart Для него это важно. Но это семантика высокого уровня. Как, черт возьми, вы хотите работать с глифами, когда вы даже не можете работать с символами Unicode. Кроме того, глифы должны поддерживаться платформой рендеринга, и если это так, эта платформа, очевидно, правильно сообщит, что пользователь теперь вставляет в позицию X + 2 (поскольку последний глиф состоял из двух символов Unicode), а не X +1. - person Šimon Tóth; 29.10.2010
comment
@MSalters: Возможно, вы имели в виду, что STL не предполагает, что std::string является UTF-16? - person Martin v. Löwis; 31.10.2010
comment
@Let_Me_Be: у вас не всегда есть механизм рендеринга, который поможет вам справиться с комбинацией символов. Способ справиться с ними, как правило, состоит в том, чтобы признать, что не существует такой вещи, как кодирование текста с фиксированной шириной (хотя вы можете добиться многого, если наложите ограничение на кодовые точки Unicode и формы нормализации, которые вы принимаете, или просто игнорируйте их существование; То же, что Microsoft делает с их обработкой символов UTF-16 вне BMP) - person Bart van Ingen Schenau; 01.11.2010
comment
@Bart Но у вас нет возможности узнать, поддерживает ли это механизм рендеринга (для меня наличие стороннего / системного механизма рендеринга и вашего собственного эквивалентно). Если платформа отображает это как два отдельных символа (хотя так не должно быть), вам нужно рассматривать это как два символа. - person Šimon Tóth; 02.11.2010
comment
@Let_Me_Be: Боюсь, опять нет. Даже если механизм рендеринга отрисовывает ä в два этапа, мой редактор не должен позволять мне вставлять e посередине, чтобы получить . Это просто чепуха. Чтобы было ясно: это ваша ответственность как программиста, а не STL или движка рендеринга. - person MSalters; 02.11.2010
comment
@MSalters Опять же, я говорю о нижнем уровне кода. Если верхний уровень действительно работает таким образом, то вы должны рассматривать ä как два отдельных символа. - person Šimon Tóth; 02.11.2010

STL работает со строками просто как с оболочкой для массива символов, поэтому size() или length() в строке STL сообщит вам, сколько элементов char или wchar_t она содержит, и не обязательно количество печатных символов, которые она будет содержать в строке. .

person CashCow    schedule 26.10.2010
comment
Итак, они реализовали basic_string таким образом, что он может обрабатывать переменную кодировку (я проверю на предмет коллизии, о которой я упоминал, и сообщу) и игнорировать необработанные строки? - person Šimon Tóth; 26.10.2010
comment
Абсолютно правильный способ OO для обработки строк переменной длины состоял бы в том, чтобы класс представлял символ переменной длины, а затем имел бы их вектор. Однако это было бы ужасно неэффективной реализацией. Лучшей реализацией является сохранение массива элементов в необработанном виде, а затем, если вам нужен еще один массив смещений для каждого символа, чтобы вы могли найти n-й символ за постоянное время после того, как он был проанализирован один раз. Вы можете сделать это оболочкой с состоянием, в том числе с состоянием, что строка на самом деле состоит из одного элемента на символ. - person CashCow; 27.10.2010
comment
Меня не очень интересует правильный путь. Для меня правильный способ - использовать переменную кодировку только для ввода и вывода. Я спрашиваю, как Microsoft обрабатывает тот факт, что у них на самом деле есть кодировка переменной длины для wchar_t и, следовательно, для wstring. - person Šimon Tóth; 27.10.2010

Предполагая, что вы говорите о типе wstring, не будет никакой обработки кодировки - он просто имеет дело с элементами wchar_t, ничего не зная о кодировке. Это просто последовательность wchar_t. Вам нужно будет решить проблемы с кодировкой, используя функциональность других функций.

person Michael Burr    schedule 26.10.2010
comment
АФАИК это правильно. Не уверен, предлагает ли UTF-16 несколько способов кодирования одной и той же кодовой точки, как это делает UTF-8. Даже если это так, пока такие функции, как wstring::operator==, не разворачивают кодировку и в этом случае возвращают true тогда и только тогда, когда строки состоят из одной и той же последовательности wchar_t, реализация совместима. Это как если бы кодировка была UCS-2. Стандарт ничего не говорит о том, разрешено ли таким функциям, как fopen, обрабатывать разные широкие строки как представляющие один и тот же файл, поэтому им разрешено обрабатывать имя как закодированное с переменной длиной. - person Steve Jessop; 26.10.2010
comment
Вам нужно иметь дело с тем фактом, что wchar_t != один символ платформы. - person Šimon Tóth; 26.10.2010
comment
@Steve Jessop: UTF-16 использует суррогатные пары (2 16-битных значения) для любого символа за пределами BMP (после U + 10000). Вам не разрешено использовать суррогатные пары для символов BMP. По сути, он обеспечивает каноническое представление так же, как это делает UTF-8, задавая кратчайшую возможную кодировку. Кроме того, строки, используемые fopen, в любом случае открыты для интерпретации платформой. C и C++ даже не указывают, чувствительны ли они к регистру. - person MSalters; 27.10.2010
comment
@Let_Me_Be: как с этим справиться? Что касается языка C++, широкий набор символов выполнения - int16_t (или uint16_t, я не могу вспомнить, подписан ли wchar_t MS). Значения строк, определяемые реализацией, - это отдельная проблема, и только здесь появляется MS и говорит, что это UTF-16. Можете ли вы привести пример некоторого кода, который ведет себя иначе с определением широкого символа Microsoft, чем это разрешено стандартом? - person Steve Jessop; 27.10.2010
comment
@Steve Стандарт на самом деле не заботится о базовом типе. Я ищу то, что MSalters опубликовал в своем ответе. Если это правда, то мне нужно знать, как обойти эту семантику. - person Šimon Tóth; 27.10.2010
comment
@Let_Me_Be: Насколько я знаю, все в ответе MSalters верно. Вам должно быть достаточно легко убедиться в этом, проведя несколько тестов, если вы в этом сомневаетесь. Я бы сказал, что способ справиться с этим - рассматривать широкие строки как выходной формат (для API Windows) и не изменять их в этом формате, но я не программист Windows, поэтому я не могу поклясться вам, что MS не предлагает лучшего способа - person Steve Jessop; 27.10.2010
comment
@ Стив, я бы сделал это, если бы мог. У меня нет компьютера с Windows рядом со мной. На самом деле да, но у меня нет прав администратора на факультетских машинах. - person Šimon Tóth; 27.10.2010
comment
@Let_Me_Be: стандарту на самом деле все равно - хорошо, я полагаю, что я действительно имел в виду, что касается языка, широкий набор символов выполнения имеет диапазон значений, который имеет int16_t. Вы правы, ему все равно, какой тип на самом деле, но важны значения. Итак, я имею в виду то, что говорит MSalters - в компиляторах MS язык думает, что старший суррогат - это символ. - person Steve Jessop; 27.10.2010
comment
@ Стив Да, спасибо. Я просто хочу, чтобы все это было ясно на 100%. - person Šimon Tóth; 27.10.2010

Две вещи:

  1. Не существует «реализации Microsoft STL». Стандартная библиотека C++, поставляемая с Visual C++, предоставляется по лицензии Dinkumware.
  2. Текущий стандарт C++ ничего не знает о Unicode и его формах кодирования. std::wstring — это просто контейнер для модулей wchar_t, которые в Windows являются 16-битными. На практике, если вы хотите сохранить строку в кодировке UTF-16 в wstring, просто примите во внимание, что вы действительно сохраняете кодовые единицы, а не кодовые точки.
person Nemanja Trifunovic    schedule 26.10.2010
comment
Безусловно, существует реализация Microsoft STL. Он может быть лицензирован, но он также модифицирован, так что это больше не оригинальная реализация Dinkumware (и даже если бы она была, она все еще является частью продукта Visual Studio). Кроме того, текущий стандарт C++ действительно что-то знает о Unicode: ISO 10646 является нормативным справочником, а универсальные имена символов интерпретируются как Unicode. Также разумно предположить, что __STDC_ISO_10646__ C99, если он определен, имеет то же значение в реализации C++ (даже если C++98 не упоминает об этом). - person Martin v. Löwis; 31.10.2010
comment
реализация Microsoft STL теперь также с открытым исходным кодом github.com/microsoft/STL - person phuclv; 21.10.2020

MSVC хранит wchar_t в wstringс. Их можно интерпретировать как 16-битные слова Unicode или что-то еще.

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

Вот набросок такой библиотеки. Это примерно наполовину меньше памяти, чем могло бы быть, но дает вам доступ на месте к глифам Unicode в файле std::string. Это зависит от наличия приличного класса array_view, но вы все равно хотите написать один из них:

struct unicode_char : array_view<wchar_t const> {
  using array_view<wchar_t const>::array_view<wchar_t const>;

  uint32_t value() const {
    if (size()==1)
      return front();
    Assert(size()==2);
    if (size()==2)
    {
      wchar_t high = front()-0xD800;
      wchar_T low = back()-0xDC00;
      return (uint32_t(high)<<10) + uint32_t(low);
    }
    return 0; // error
  }
  static bool is_high_surrogate( wchar_t c ) {
    return (c >= 0xD800 && c <= 0xDBFF);
  }
  static bool is_low_surrogate( wchar_t c ) {
    return (c >= 0xDC00 && c <= 0xDFFF);
  }
  static unicode_char extract( array_view<wchar_t const> raw )
  {
    if (raw.empty())
      return {};
    if (raw.size()==1)
      return raw;
    if (is_high_surrogate(raw.front()) && is_low_surrogate(*std::next(raw.begin())))
      return {raw.begin(), raw.begin()+2);
    return {raw.begin(), std::next(raw.begin())};
  }
};
static std::vector<unicode_char> as_unicode_chars( array_view<wchar_t> raw )
{
  std::vector<unicode_char> retval;
  retval.reserve( raw.size() ); // usually 1:1
  while(!raw.empty())
  {
    retval.push_back( unicode_char::extract(raw) );
    Assert( retval.back().size() <= raw.size() );
    raw = {raw.begin() + retval.back().size(), raw.end()};
  }
  return retval;
}
struct unicode_glyph {
  std::array< unicode_char, 3 > buff;
  std::size_t count=0;
  unicode_char const* begin() const {
    return buff.begin();
  }
  unicode_char const* end() const {
    return buff.begin()+count;
  }
  std::size_t size() const { return count; }
  bool empty() { return size()==0; }
  unicode_char const& front() const { return *begin(); }
  unicode_char const& back() const { return *std::prev(end()); }
  array_view< unicode_char const > chars() const { return {begin(), end()}; }
  array_view< wchar_t const > wchars() const {
    if (empty()) return {};
    return { front().begin(), back().end() };
  }

  void append( unicode_char next ) {
    Assert(count<3);
    buff[count++] = next;
  }
  unicode_glyph() {}

  static bool is_diacrit(unicode_char c) const {
    auto v = c.value();
    return is_diacrit(v);
  }
  static bool is_diacrit(uint32_t v) const {
    return
      ((v >= 0x0300) && (v <= 0x0360))
    || ((v >= 0x1AB0) && (v <= 0x1AFF))
    || ((v >= 0x1DC0) && (v <= 0x1DFF))
    || ((v >= 0x20D0) && (v <= 0x20FF))
    || ((v >= 0xFE20) && (v <= 0xFE2F));
  }
  static size_t diacrit_count(unicode_char c) const {
    auto v = c.value();
    if (is_diacrit(v))
      return 1 + ((v >= 0x035C)&&(v<=0x0362));
    else
      return 0;
  }
  static unicode_glyph extract( array_view<const unicode_char> raw ) {
    unicode_glyph retval;
    if (raw.empty())
      return retval;
    if (raw.size()==1)
    {
      retval.append(raw.front());
      return retval;
    }
    retval.count = diacrit_count( *std::next(raw.begin()) )+1;
    std::copy( raw.begin(), raw.begin()+retval.count, retval.buff.begin() );
    return retval;
  }
};
static std::vector<unicode_glyph> as_unicode_glyphs( array_view<unicode_char> raw )
{
  std::vector<unicode_glyph> retval;
  retval.reserve( raw.size() ); // usually 1:1
  while(!raw.empty())
  {
    retval.push_back( unicode_glyph::extract(raw) );
    Assert( retval.back().size() <= raw.size() );
    raw = {raw.begin() + retval.back().size(), raw.end()};
  }
  return retval;
}
static std::vector<unicode_glyph> as_unicode_glyphs( array_view<wchar_t> raw )
{
  return as_unicode_glyphs( as_unicode_chars( raw ) );
}

более умный фрагмент кода будет генерировать unicode_chars и unicode_glyphs на лету с помощью какого-либо фабричного итератора. Более компактная реализация будет отслеживать тот факт, что конечный указатель предыдущего и начальный указатель следующего всегда идентичны, и объединять их вместе. Другой оптимизацией может быть использование оптимизации небольших объектов для глифа, основанное на предположении, что большинство глифов представляют собой один символ, и использование динамического распределения, если их два.

Обратите внимание, что я рассматриваю CGJ как стандартный диакрит, а двойные диакриты — как набор из 3 символов, которые образуют один (юникод), но полудиакриты не объединяют вещи в один глиф. Это все сомнительные варианты.

Это было написано в приступе бессонницы. Надеюсь, это хоть немного сработает.

person Yakk - Adam Nevraumont    schedule 24.09.2014