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

Подпись char не стандартизирована. Следовательно, существуют типы signed char и unsigned char. Поэтому функции, работающие с одним символом, должны использовать тип аргумента, который может содержать как знаковый char, так и беззнаковый 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;
}

person Igor Liferenko    schedule 23.11.2016    source источник
comment
Возможный дубликат Почему char не подписан и не подписан , а wchar_t есть?   -  person phuclv    schedule 23.11.2016


Ответы (1)


TL;DR:

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

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


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

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

Чтобы быть точным, реализация должна определять 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() и другими функциями stdio, так и по историческим причинам. В прежние времена вы вообще не могли передать 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;
}
person John Bollinger    schedule 23.11.2016
comment
Стандартные библиотечные функции, работающие с отдельными символами, можно было бы легко определить в терминах типа char — это сделало бы код непереносимым. - person Igor Liferenko; 23.11.2016
comment
Спасибо, @M.M., я удалил оскорбительный текст. - person John Bollinger; 23.11.2016
comment
Ваш пример getchar() неуместен - спасибо, я это исправил. - person Igor Liferenko; 23.11.2016
comment
В дальнейшем нельзя изменить тип аргумента - тип аргумента int оправдан - его не нужно менять (см. первый пример) - person Igor Liferenko; 23.11.2016
comment
@IgorLiferenko, повторяю: стандартные библиотечные функции или действительно любые библиотечные функции могут быть определены для приема параметров типа char без ущерба для переносимости. Нельзя переопределитьсуществующие библиотечные функции, чтобы они принимали аргументы разных типов, а тип char может не подходить для конкретных целей данной функции, но тот факт, что подписанность char определяется реализацией, по своей сути не влияет на переносимость таких функций. - person John Bollinger; 23.11.2016
comment
Чтобы сделать код переносимым, мы должны использовать (unsigned char) cast. Но на платформе, где char по умолчанию подписан, это вызовет предупреждения (см. первый пример). Как заставить функцию принимать char и избавиться от предупреждений о преобразовании? Может быть, это стоит задать отдельным вопросом? - person Igor Liferenko; 23.11.2016
comment
См. ОБНОВЛЕНИЕ в ОП. - person Igor Liferenko; 23.11.2016
comment
@IgorLiferenko, если вы хотите передать значение char одной из функций классификации символов, вы должны привести его к unsigned char, потому что только это гарантирует получение значения int, которое вам нужно предоставить этим функциям. Это следствие спецификаций этих конкретных функций в терминах unsigned char значений, а не общее следствие того, что подписанность char определяется реализацией. - person John Bollinger; 23.11.2016
comment
@IgorLiferenko, теперь я добавил ответ на ваше обновление. - person John Bollinger; 23.11.2016
comment
Это прекрасный ответ. Один нит: wint_t не требуется для представления таких значений - я думаю, что wint_t на самом деле требуется для представления хотя бы одного из этих значений, которое служит сигнальным значением ошибки, на которое вы ссылались ранее в своем ответе. - person Haldean Brown; 23.11.2016
comment
@HaldeanBrown, win_t требуется, чтобы иметь возможность представлять хотя бы одно значение, которое не соответствует ни одному символу какого-либо расширенного набора символов любой поддерживаемой локали, но это значение не обязательно должно быть одним из тех, которые может представлять wchar_t. Например, wchar_t может быть 22-разрядным целым числом без знака, способным представлять каждую кодовую точку Unicode плюс некоторые значения, которые больше любой кодовой точки Unicode, а wint_t — это 22-разрядное целое число со знаком, способное представлять каждую кодовую точку Unicode плюс некоторые значения. отрицательные значения. - person John Bollinger; 23.11.2016
comment
@HaldeanBrown, но я добавил пояснение по этому поводу в ответ, спасибо. - person John Bollinger; 23.11.2016
comment
Почему в char API мы приводим к (unsigned char) при вызове, скажем, islower(), а в wchar_t API мы приводим к (wint_t) (явно или неявно). Логика говорит, что мы должны привести к (unsigned wchar_t). См. stackoverflow.com/questions/43061489. - person Igor Liferenko; 28.03.2017