Почему нет типов "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;
}