为什么没有 "unsigned wchar_t" 和 "signed wchar_t" 类型?

Why there are no "unsigned wchar_t" and "signed wchar_t" types?

char 的符号没有标准化。因此有 signed charunsigned char 类型。因此,使用单个字符的函数必须使用可以同时包含有符号字符和无符号字符的参数类型(这 type 被选择为 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)

无需显式类型转换就可以工作的是自动升级 从 charint.

此外,引入wchar_t的ISO C90标准什么也没说 具体关于 wchar_t.

的表示

glibc 参考中的一些引述:

it would be legitimate to define wchar_t as char

if wchar_t is defined as char the type wint_t must be defined as int due to the parameter promotion.

所以,wchar_t完全可以定义为char,也就是说类似的规则 对于宽字符类型必须适用,即可能有实现 wchar_t 是正数,可能存在 wchar_t 是负数的实现。 由此可以得出,必须存在 unsigned wchar_tsigned wchar_t 类型(原因与 unsigned charsigned char 类型相同)。

私人交流表明允许实现支持广泛 仅具有 >=0 值的字符(独立于 wchar_t 的符号)。有人知道这是什么意思吗? thin 是指当 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_tsigned wchar_t 类型?

更新

标准是否保证在以下两个程序中转换为 unsigned intint 是正确的? (我只是将 wint_twchar_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;
}

长话短说:

Why there are no unsigned wchar_t and signed wchar_t types?

因为 C 的宽字符处理工具被定义为不需要它们。


更详细,

The signedness of char is not standardized.

准确地说,“实现应将 char 定义为具有与 signed char 或 unsigned char 相同的范围、表示和行为。” (C2011, 6.2.5/15)

Hence there are signed char and unsigned char types.

“因此”意味着因果关系,这很难说清楚,但当你想处理数字而不是字符时,signed charunsigned char 肯定更合适。

Therefore functions which work with single character must use the argument type which can hold both signed char and unsigned char

不,一点也不。使用单个字符的标准库函数可以根据类型 char 轻松定义,而不管该类型是否已签名,因为库实现 确实 知道其签名。如果这是一个问题,那么它也同样适用于字符串函数——char 将毫无用处。

您的 getchar() 示例不恰当。它 returns int 而不是字符类型,因为它需要能够 return 一个不对应任何字符的错误指示符。此外,您提供的代码与随附的警告消息不对应:它包含从 intunsigned char 的转换,但没有从 charunsigned char 的转换。

其他一些字符处理函数接受 int 参数或 return 类型 int 的值,既是为了与 getchar() 和其他 stdio 函数兼容,也是出于历史原因.在过去,您实际上根本无法传递 char——它总是会被提升为 int,而这正是函数将(并且必须)接受的。尽管语言在进化,但以后不能更改参数类型。

Further, the ISO C90 standard, where wchar_t was introduced, does not say anything specific about the representation of wchar_t.

C90 不再真正相关,但毫无疑问它说的内容与 C2011 (7.19/2) 非常相似,它将 wchar_t 描述为

an integer type whose range of values can represent distinct codes for all members of the largest extended character set specified among the supported locales [...].

您对 glibc 参考的引用是非权威性的,除非可能仅针对 glibc。在任何情况下,它们似乎都是评论,而不是规范,并且不清楚你为什么提出它们。当然,至少第一个是正确的。参考标准,如果给定实现支持的语言环境中指定的最大扩展字符集的所有成员都可以放入 char,那么该实现可以将 wchar_t 定义为 char。这样的实现在过去比现在普遍得多。

你问了几个问题:

Private communication reveals that an implementation is allowed to support wide characters with >=0 value only (independently of signedness of wchar_t). Anybody knows what this means?

我认为这意味着与您交流的人不知道他们在说什么,或者他们在说什么与 C 标准提出的要求不同。您会发现在实践中,字符集仅使用非负字符代码定义,但这不是 C 标准的约束。

Does thin mean that when wchar_t is 16-bit type (for example), we can only use 15 bits to store the value of wide character?

C 标准没有说明或暗示这一点。您可以将任何支持的字符的值存储在 wchar_t 中。特别是,如果实现支持包含超过 32767 个字符代码的字符集,那么您可以将它们存储在 wchar_t.

In other words, is it true that a sign-extended wchar_t is a valid value?

C 标准没有说明或暗示这一点。它甚至没有说 wchar_t 是否是有符号类型(如果不是,那么符号扩展对它没有意义)。如果它是有符号类型,则无法保证对表示某些受支持字符集中的字符的值进行符号扩展(原则上该值可以为负数)是否会产生也表示该字符中的字符的值集,或任何其他支持的字符集。将 wchar_t 值加 1 也是如此。

Also, private communication reveals that the standard requires that any valid value of wchar_t must representable by wint_t. Is it true?

这取决于你所说的“有效”是什么意思。标准说 wint_t

is an integer type unchanged by default argument promotions that can hold any value corresponding to members of the extended character set, as well as at least one value that does not correspond to any member of the extended character set.

(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() 定义为 return 相同的值——-1 或字符值,如有必要,转换为 unsigned char .另一方面,宽字符分类函数接受wint_t类型的参数,它可以表示所有宽字符的值不变,因此不需要转换。

您在这方面声称

We need to use iswlower((unsigned wchar_t)wc) here, but there is no unsigned wchar_t type.

不,也许吧。您不需要将 wchar_t 参数转换为 iswlower() 任何其他类型,特别是,您不需要将其转换为显式无符号类型。宽字符分类函数在这方面与常规字符分类函数不同,它的设计是事后诸葛亮。至于unsigned wchar_t,C不要求存在这样的类型,所以可移植代码不应该使用它,但它可能存在于某些实现中。


关于附加到问题的更新:

Are the standards saying that casting to unsigned int and to int in the following two programs is guaranteed to be correct? (I just replaced wint_t and wchar_t to their actual meaning in glibc)

该标准没有提及一般的符合性实现。但是,我假设您的意思是专门询问 wchar_tint 并且 wint_tunsigned int.

的符合性实现

在这样的实现中,您的第一个程序是有缺陷的,因为它没有考虑 getwchar() return 和 WEOF 的可能性。将 WEOF 转换为类型 wchar_t,如果这样做不会引发信号,则不能保证产生对应于任何宽字符的值。因此,将这种转换的结果传递给 putwchar() 不会表现出定义的行为。此外,如果 WEOF 定义为与 UINT_MAX 相同的值(int 无法表示),则该值到 int 的转换具有独立的实现定义行为putwchar() 电话。

另一方面,我认为你纠结的关键点是,如果第一个程序中getwchar()编辑的值return不是WEOF,那么它通过转换为 wchar_t 保证是不变的。在这种情况下,您的第一个程序将按预期执行,但不需要强制转换为 int(或 wchar_t)。

同样,如果宽字符文字对应于适用扩展字符集中的字符,则第二个程序是正确的,但转换是不必要的并且没有任何改变。这种字面量的 wchar_t 值保证可以用类型 wint_t 表示,因此转换会更改其操作数的类型,但不会更改值。 (但如果文字不对应于扩展字符集中的字符,则行为是实现定义的。)

另一方面,如果您的 objective 是要编写严格符合标准的代码,那么正确的做法,以及这些特定宽字符函数的预期使用模式,应该是这样的:

#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;
}