Почему нет типов "unsigned wchar_t" и "signature wchar_t"?

Подпись char не стандартизирована. Следовательно, есть signed char а также unsigned char типы. Поэтому функции, работающие с одним символом, должны использовать тип аргумента, который может содержать как символ со знаком, так и символ без знака (этот тип был выбран как int), потому что если тип аргумента был charмы бы получили предупреждения о преобразовании типов от компилятора (если используется -Wconversion) в коде, подобном следующему:

char c = 'ÿ';
if (islower((unsigned char) c)) ...

warning: conversion to ‘char’ from ‘unsigned char’ may change the sign of the result

(здесь мы рассмотрим, что произойдет, если тип аргумента islower() будет char)

И то, что заставляет его работать без явного приведения типов, это автоматическое продвижение от char в int,

Кроме того, стандарт ISO C90, где wchar_t был введен, не говорит ничего конкретного о представлении wchar_t,

Некоторые цитаты из ссылки на glibc:

было бы правомерно определить wchar_t как char

если wchar_t определяется как char тип wint_t должен быть определен как int из-за параметра продвижения.

Так, wchar_t вполне может быть определена как char, что означает, что должны применяться аналогичные правила для широких символов, т. е. могут быть реализации, гдеwchar_t положительно, и могут быть реализации, где wchar_t отрицательно. Из этого следует, что должно существовать unsigned wchar_t а также signed wchar_t типы (по той же причине, что и unsigned char а также signed char типов).

Частное общение показывает, что реализация может поддерживать широкие символы только со значением>=0 (независимо от подписи wchar_t). Кто-нибудь знает, что это значит? Означает ли это, что когда wchar_t 16-битный тип (например), мы можем использовать только 15 бит для хранения значения широких символов? Другими словами, правда ли, что знак продлен wchar_t является действительным значением? Смотрите также этот вопрос.

Кроме того, личное общение показывает, что стандарт требует, чтобы любое действительное значение wchar_t должен быть представлен wint_t, Это правда?

Рассмотрим этот пример:

#include <locale.h>
#include <ctype.h>
int main (void)
{
  setlocale(LC_CTYPE, "fr_FR.ISO-8859-1");

  /* 11111111 */
  char c = 'ÿ';

  if (islower(c)) return 0;
  return 1;
}

Чтобы сделать его переносимым, нам понадобится приведение к '(unsigned char)'. Это необходимо, потому что char может быть эквивалентом signed char, в этом случае байт, в котором установлен верхний бит, будет расширен знаком при преобразовании в int, получая значение, которое находится за пределами диапазона unsigned char,

Теперь, почему этот сценарий отличается от следующего примера для широких символов?

#include <locale.h>
#include <wchar.h>
#include <wctype.h>
int main(void)
{
  setlocale(LC_CTYPE, "");
  wchar_t wc = L'ÿ';

  if (iswlower(wc)) return 0;
  return 1;
}

Нам нужно использовать iswlower((unsigned wchar_t)wc) здесь, но нет unsigned wchar_t тип.

Почему нет unsigned wchar_t а также signed wchar_t типы?

ОБНОВИТЬ

Стандарты говорят, что приведение к unsigned int и к int в следующих двух программах гарантированно будет правильно? (Я только заменил wint_t а также wchar_t к их действительному значению в glibc)

#include <locale.h>
#include <wchar.h>
int main(void)
{
  setlocale(LC_CTYPE, "en_US.UTF-8");
  unsigned int wc;
  wc = getwchar();
  putwchar((int) wc);
}

-

#include <locale.h>
#include <wchar.h>
#include <wctype.h>
int main(void)
{
  setlocale(LC_CTYPE, "en_US.UTF-8");
  int wc;
  wc = L'ÿ';
  if (iswlower((unsigned int) wc)) return 0;
  return 1;
}

1 ответ

Решение

TL;DR:

Почему нет неподписанных типов wchar_t и wchar_t со знаком?

Потому что широкие возможности обработки C были определены так, что они не нужны.


Более подробно,

Подпись char не стандартизирована.

Чтобы быть точным: "Реализация должна определять char, который будет иметь тот же диапазон, представление и поведение, что и char со знаком или без знака". (C2011, 6.2.5/15)

Следовательно, есть signed char а также unsigned char типы.

"Следовательно" подразумевает причинно-следственную связь, о которой было бы трудно спорить ясно, но, безусловно, signed char а также unsigned char более уместны, когда вы хотите обрабатывать числа, а не символы.

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

Нет, совсем нет. Стандартные библиотечные функции, которые работают с отдельными символами, могут быть легко определены в терминах типа char независимо от того, подписан ли этот тип, потому что реализация библиотеки знает его подпись. Если бы это было проблемой, то это также применимо и к строковым функциям - char было бы бесполезно.

Ваш пример getchar() это не уместно. Возвращается int а не тип символа, потому что он должен иметь возможность возвращать индикатор ошибки, который не соответствует ни одному символу. Кроме того, код, который вы предоставляете, не соответствует сопровождающему предупреждающему сообщению: он содержит преобразование из int в unsigned char, но без конвертации из char в unsigned char,

Некоторые другие функции обработки символов принимают int параметры или возвращаемые значения типа int оба для совместимости с getchar() и другие функции, и по историческим причинам. В былые времена вы не могли на самом деле пройти char вообще - это всегда будет повышаться до int и это то, что функции будут (и должны) принимать. Позже нельзя изменить тип аргумента, несмотря на эволюцию языка.

Кроме того, стандарт ISO C90, где wchar_t был введен, не говорит ничего конкретного о представлении wchar_t,

C90 на самом деле уже не актуален, но, без сомнения, он говорит нечто очень похожее на C2011 (7.19/2), в котором описывается wchar_t как

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

Ваши цитаты из ссылки на glibc не являются авторитетными, за исключением, возможно, только для glibc. В любом случае они кажутся комментариями, а не спецификациями, и неясно, почему вы их поднимаете. Конечно, хотя, по крайней мере, первое правильно. Ссылаясь на стандарт, если все члены самого большого расширенного набора символов, указанного среди локалей, поддерживаемых данной реализацией, могли бы поместиться в char тогда эта реализация может определить wchar_t как char, Такие реализации раньше были гораздо более распространенными, чем сегодня.

Вы задаете несколько вопросов:

Частное общение показывает, что реализация может поддерживать широкие символы только со значением>=0 (независимо от подписи wchar_t). Кто-нибудь знает, что это значит?

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

Означает ли это, что когда wchar_t 16-битный тип (например), мы можем использовать только 15 бит для хранения значения широких символов?

Стандарт C не говорит и не подразумевает этого. Вы можете сохранить значение любого поддерживаемого символа в wchar_t, В частности, если реализация поддерживает набор символов, содержащий коды символов, превышающие 32767, вы можете сохранить их в wchar_t,

Другими словами, правда ли, что расширенный знак wchar_t является допустимым значением?

Стандарт C не говорит и не подразумевает этого. Это даже не говорит ли wchar_t тип со знаком (если нет, то расширение знака для него не имеет смысла). Если это тип со знаком, то нет никакой гарантии, что расширение значения знака, представляющего символ в некотором поддерживаемом наборе символов (это значение, в принципе, может быть отрицательным), приведет к значению, которое также представляет символ в этом символе или в любом другом поддерживаемом наборе символов. То же самое относится и к добавлению 1 к wchar_t значение.

Кроме того, личное общение показывает, что стандарт требует, чтобы любое действительное значение wchar_t должен быть представлен wint_t, Это правда?

Это зависит от того, что вы подразумеваете под "действительным". Стандарт говорит, что wint_t

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

(C2011, 7.29.1 / 2)

wchar_t должен иметь возможность хранить любое значение, соответствующее члену расширенного набора символов, в любой поддерживаемой локали. wint_t должен иметь возможность хранить все эти значения тоже. Может быть, однако, что wchar_t способен представлять значения, которые не соответствуют ни одному символу в любом поддерживаемом наборе символов. Такие значения действительны в том смысле, что тип может их представлять. wint_t не требуется, чтобы иметь возможность представлять такие значения.

Например, если в наибольшем расширенном наборе символов из любой поддерживаемой локали используются коды символов, не превышающие 32767, то реализация будет свободна для реализации wchar_t как 16-разрядное целое число без знака и wint_t как 16-разрядное целое число со знаком. Значения, представляемые wchar_t которые не соответствуют расширенным символам, тогда не могут быть представлены wint_t (но wint_t все еще имеет много кандидатов на его требуемое значение, которое не соответствует ни одному символу).

Что касается символьных и широкоформатных классификационных функций, единственный ответ заключается в том, что различия просто возникают из-за разных спецификаций. char классификационные функции определены для работы с теми же значениями, что getchar() определяется как возвращаемое - либо -1, либо значение символа, преобразованное, если необходимо, в unsigned char, Функции классификации широких символов, с другой стороны, принимают аргументы типа wint_t, который может представлять значения всех широких символов без изменений, поэтому нет необходимости в преобразовании.

Вы утверждаете в этой связи, что

Нам нужно использовать iswlower((unsigned wchar_t)wc) здесь, но нет unsigned wchar_t тип.

Нет и может быть Вам не нужно конвертировать wchar_t аргумент iswlower() к любому другому типу, и, в частности, вам не нужно преобразовывать его в явно неподписанный тип. Функции классификации широких символов в этом отношении не аналогичны обычным функциям классификации символов, разработанным с учетом ретроспективного взгляда. Что касается unsigned wchar_t C не требует, чтобы такой тип существовал, поэтому переносимый код не должен его использовать, но он может существовать в некоторых реализациях.


По поводу обновления добавлен вопрос:

Стандарты говорят, что приведение к unsigned int и int в следующих двух программах гарантированно будет правильным? (Я просто заменил wint_t и wchar_t на их действительное значение в glibc)

Стандарт ничего не говорит о соответствии реализаций в целом. Я предполагаю, однако, что вы хотите спросить конкретно о соответствующих реализациях, для которых wchar_t является int а также wint_t является unsigned int,

В такой реализации ваша первая программа имеет недостатки, поскольку она не учитывает вероятность того, что getwchar() возвращается WEOF, преобразование WEOF печатать wchar_t Если это не приводит к повышению уровня сигнала, не гарантируется получение значения, соответствующего любому широкому символу. Передача результата такого преобразования в putwchar() поэтому не проявляет определенного поведения. Более того, если WEOF определяется с тем же значением, что и UINT_MAX (который не может быть представлен int) затем преобразование этого значения в int имеет поведение, определяемое реализацией, независимо от putwchar() вызов.

С другой стороны, я думаю, что ключевым моментом, с которым вы боретесь, является то, что если значение, возвращаемое getwchar() в первой программе нет WEOF тогда он гарантированно останется неизменным при преобразовании в wchar_t, Ваша первая программа будет работать так, как кажется в этом случае, но приведение к int (или же wchar_t) не нужно.

Точно так же вторая программа корректна при условии, что литерал широких символов соответствует символу в соответствующем расширенном наборе символов, но приведение не является необходимым и ничего не меняет. wchar_t значение такого литерала гарантированно представимо по типу wint_t Таким образом, приведение изменяет тип своего операнда, но не значение. (Но если литерал не соответствует символу в расширенном наборе символов, то поведение определяется реализацией.)

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

#include <locale.h>
#include <wchar.h>
int main(void)
{
  setlocale(LC_CTYPE, "en_US.UTF-8");
  wint_t wc = getwchar();
  if (wc != WEOF) {
    // No cast is necessary or desirable
    putwchar(wc);
  }
}

и это:

#include <locale.h>
#include <wchar.h>
#include <wctype.h>
int main(void)
{
  setlocale(LC_CTYPE, "en_US.UTF-8");
  wchar_t wc = L'ÿ';
  // No cast is necessary or desirable
  if (iswlower(wc)) return 0;
  return 1;
}
Другие вопросы по тегам