C++ 标准是不是要求对 wchar_t 进行编码?

Posted

技术标签:

【中文标题】C++ 标准是不是要求对 wchar_t 进行编码?【英文标题】:Does the C++ standard mandate an encoding for wchar_t?C++ 标准是否要求对 wchar_t 进行编码? 【发布时间】:2016-12-10 18:34:36 【问题描述】:

以下是我的 2014 年标准草案 N4140 副本的一些节选

22.5 标准代码转换方面[locale.stdcvt]

3 对于 codecvt_utf8codecvt_utf16codecvt_utf8_utf16 三个代码转换方面中的每一个: (3.1) — Elem 是宽字符类型,例如wchar_tchar16_tchar32_t

4 对于方面codecvt_utf8: (4.1) — facet 应在程序中在 UTF-8 多字节序列和 UCS2 或 UCS4(取决于 Elem 的大小)之间转换。

对这两段的一种解释是wchar_t 必须编码为 UCS2 或 UCS4。我不太喜欢它,因为如果它是真的,那么我们在库描述中就隐藏着语言的一个重要属性。我试图找到这个属性的更直接的陈述,但无济于事。

另一种解释是 wchar_t 编码不需要是 UCS2 或 UCS4,并且在不是的实现中,codecvt_utf8 不适用于 wchar_t。我也不太喜欢这种解释,因为如果它是真的,而且 charwchar_t 原生编码都不是 Unicode,那么似乎没有办法在这些原生编码和 Unicode 之间进行可移植的转换。

这两种解释中哪一种是正确的?还有一个是我忽略的吗?

澄清我不是在询问关于wchar_t 是否适合软件开发的一般意见,或者wchar_t 的属性可以从其他地方获得。我对标准的这两个特定段落感兴趣。我试图了解这些特定段落包含或不包含什么。

说明 2。如果 4.1 说“方面应在 UTF-8 多字节序列和 UCS2 或 UCS4 或当前全局语言环境对 wchar_t 施加的任何编码之间进行转换”,就不会有问题。它没有。它说它说什么。看来,如果使用std::codecvt_utf8<wchar_t>,则不管当前的全局语言环境如何,最终都会得到一堆编码为UCS2 或UCS4 的wchar_t。 (无法为codecvt_utf8 指定语言环境或任何字符转换方面)。所以这个问题可以改写成这样:转换结果是否可以直接与当前的全局语言环境(和/或任何可能的语言环境)一起用于输出、wctype 查询等等?如果没有,它对 有什么用处? (如果上面的第二种解释是正确的,答案似乎是“没有”)。

【问题讨论】:

wchar_t 是不可移植的。例如,在 Unix 上是 UTF-32,在 Windows 上是 UTF-16(不是 UCS2) wchar_t 是一个整数类型。是什么让您认为它具有固定编码?它可以存储数字7,您可以将其解释为“用户单击了左侧按钮”。在其他地方,您可以将7 中的wchar_T 解释为“激活火警”,在其他地方解释为小写a。有趣的问题是当您从输入等中读取时会发生什么,但这不是 wchar_t 的编码,而是 io 所做的编码......方面描述 使用该方面的编码结果是什么关于流式操作... wchar_t 必须大于char,就是这样.. wchar_t 只是能够包含UCS2UCS4,它不是强制到的。 一个编码是从数字到意义的分配。类型不具备这样的语义。 【参考方案1】:

没有。

wchar 只需要保存编译器支持的最大语言环境。理论上可以放入一个字符中。

类型 wchar_t 是一个独特的类型,其值可以表示支持的语言环境 (22.3.1) 中指定的最大扩展字符集的所有成员的不同代码。

— C++ [basic.fundamental] 3.9.1/5

因此它甚至不需要支持 Unicode

wchar_t 的宽度是特定于编译器的,可以小到 8 位。因此,需要在任何 C 或 C++ 编译器之间移植的程序不应使用 wchar_t 来存储 Unicode 文本。 wchar_t 类型用于存储编译器定义的宽字符,在某些编译器中可能是 Unicode 字符。

ISO/IEC 10646:2003 Unicode 标准 4.0

【讨论】:

【参考方案2】:

让我们区分wchar_t 和使用L 前缀构建的字符串文字。

wchar_t 只是一个整数类型,可能大于char

使用L 前缀的字符串文字将使用wchar_t 字符生成字符串。这究竟意味着什么取决于实现。不要求此类文字使用任何特定编码。他们可能使用 UTF-16、UTF-32 或其他与 Unicode 完全无关的东西。

因此,如果您希望 保证 在所有平台上以 Unicode 格式编码的字符串文字,请为字符串文字使用 u8uU 前缀.

对这两段的一种解释是 wchar_t 必须编码为 UCS2 或 UCS4。

不,这不是一个有效的解释。 wchar_t 没有编码;它只是一种类型。编码的是 data。以L 为前缀的字符串文字可能会也可能不会在 UCS2 或 UCS4 中编码。

如果您提供codecvt_utf8 以UCS2 或UCS4 编码的wchar_ts 字符串(根据sizeof(wchar_t)),那么它将起作用。但不是因为wchar_t;它之所以有效,是因为您提供的 数据 编码正确。

如果 4.1 说“方面应在 UTF-8 多字节序列和 UCS2 或 UCS4 或当前全局语言环境对 wchar_t 施加的任何编码之间转换”,则没有问题。

这些codecvt_* 方面的全部意义在于执行与语言环境无关的 转换。如果您想要依赖于语言环境的转换,则不应使用它们。您应该改用全局 codecvt 构面。

【讨论】:

@n.m.:我对这些段落的解释是,它们的意思正是他们所说的。你对它们的解释是混乱的,因为你对它们使用的词的理解是混乱的。这就是为什么我解释了这些词的含义。 wchar_t 不是编码。它没有编码;它只是一种类型。 各种语言环境方面对wchar_t 施加了一种或多种编码。我在问它们中的任何一个或所有都必须是 UCS-whatever。 @n.m.:不,语言环境方面对wchar_t 施加nothing。它们对某些操作进行编码。因此,您可以通过使用将编码强加到流上的语言环境来为带有 iostream 的编码构建一个字符串。但这与wchar_t本身的行为无关;这只影响存储在wchar_t 数组中的数据。而且语言环境不会对codecvt 方面施加任何影响。 “他们对某些操作施加编码” 在我的书中,这就是对wchar_t 施加编码。我正在构建字符串以对它们执行操作,而不是将它们框起来并将它们挂在墙上。 codecvt 是一个语言环境方面,语言环境只是拥有它们。 我想要一个非常简单的东西,能够以与 wchar_t 的其他用途一致的方式将 UTF-8 转换为 wchar_t。即,打印到(未篡改)wcout,与 L"" 文字进行比较,和/或查询 isw... 位,而不触及我当前的全局语言环境或流语言环境。我知道我可以将 UTF-8 转换为 UCS4 并将这些值填充到 wchar_t,但这似乎是一个相当无用的练习,除非我碰巧知道我提到的操作实际上使用 UCS4。【参考方案3】:

您的第一个结论似乎由Microsoft 分享,他列举了可能的选项,并注意 UTF-16,尽管“广泛使用 [原文如此]”不是有效的编码。

QNX 也使用了相同的措辞,它指向措辞的来源:QNX 和 Microsoft 都从 Dinkumware 派生其标准库实现。

现在,碰巧的是,Dinkumware 也是 N2401 的作者,它介绍了这些类。所以我要站在他们一边。

【讨论】:

看来你的第一个结论是微软同意的 - 你能详细说明一下吗?我可以从该链接中获得的唯一信息是 UCS-* / UTF-* 的定义,而不是 wchar_t 必须编码为 UCS-2/4。 嗯,微软说“表示在编码为 UCS-2 或 UCS-4 ... 的宽字符之间转换的语言环境方面”。这似乎并不意味着没有其他可能性。我记得在 wchar_t 是 JIS 之一的机器上工作,当前的 C++ 不支持这样的环境吗? @Holt:该位遵循“...几种字符编码。对于宽字符...:”,然后是定义 UCS2、UCS4 和 UTF-16 的列表。没有暗示表明该列表只是示例;它似乎是详尽无遗的。 @MSalters 这些是唯一出现在标准中的,因此它们只是定义了标准中术语的可能解释。至少我是这么看的。【参考方案4】:

由于Elem 可以是wchar_tchar16_tchar32_t,因此第4.1 条没有说明必需的wchar_t 编码。它说明了所执行的转换。

从措辞中可以清楚地看出,转换是在 UTF-8 和 UCS-2 或 UCS-4 之间进行的,具体取决于 Elem 的大小。因此,如果wchar_t 是 16 位,则转换将使用 UCS-2,如果是 32 位,则转换为 UCS-4。

为什么标准提到 UCS-2 和 UCS-4 而不是 UTF-16 和 UTF-32 ?因为codecvt_utf8 会将多字节UTF8 转换为单个宽字符:

UCS-2是unicode的子集,但是有no surogate pair encoding与UTF-16相反 UCS-4 现在与 UTF-32 相同(但看看越来越多的表情符号,也许有一天 32 位不够用,你会得到一个 UTF-64 和 UTF32 代理codecvt_utf8 不支持的对)

虽然我不清楚会发生什么,但如果 UTF-8 文本包含一个序列,该序列对应于用于接收 char16_t 的 UCS-2 中不可用的 unicode 字符。

【讨论】:

您的最后一句话:转换只会让 IMO 失败。【参考方案5】:

wchar_t 只是一个完整的文字。它有一个最小值、一个最大值等。

它的大小没有标准固定。

如果足够大,您可以将 UCS-2 或 UCS-4 数据存储在 wchar_t 的缓冲区中。无论您使用哪种系统,这都是正确的,因为 UCS-2 和 UCS-4 以及 UTF-16 和 UTF-32 只是对按顺序排列的整数值的描述。

在 C++11 中,有std API 可以读取或写入假定数据具有这些编码的数据。在 C++03 中,有使用当前语言环境读取或写入数据的 API。

22.5 标准代码转换方面 [locale.stdcvt]

3 对于三个代码转换方面codecvt_utf8、codecvt_utf16和codecvt_utf8_utf16中的每一个:

(3.1) — Elem 是宽字符类型,例如 wchar_t、char16_t 或 char32_t。

4 对于分面codecvt_utf8:

(4.1) — facet 应在程序内的 UTF-8 多字节序列和 UCS2 或 UCS4(取决于 Elem 的大小)之间转换。

所以这里codecvt_utf8_utf16 一方面处理utf8,另一方面处理UCS2 或UCS4(取决于Elem 的大小)。它会进行转换。

Elem(宽字符)被假定为 UCS2 或 UCS4 编码,具体取决于它的大小。

这并不意味着wchar_t 被这样编码,它只是意味着这个操作将wchar_t 解释为被这样编码

UCS2 或 UCS4 如何进入 Elem 并不是标准的这一部分关心的问题。也许你用十六进制常量将它设置在那里。也许你是从 io 读到的。也许你是在飞行中计算出来的。也许您使用了高质量的随机数生成器。也许您将ascii 字符串的位值加在一起。也许您计算了log* 的定点近似值,即月球将地球日变化 1 秒所需的秒数。 不是这些段落的问题。这些段落只是规定如何修改和解释位。

类似的主张在其他情况下也成立。这并不强制要求 wchar_t 具有什么格式。它只是说明这些方面如何解释wchar_tchar16_tchar32_tchar8_t(阅读或写作)。

wchar_t 交互的其他方式使用不同的方法来规定如何解释wchar_t 的值。

iswalpha 使用(全局)语言环境来解释 wchar_t,例如。在某些当地人中,wchar_t 可能是 UCS2。在其他情况下,可能是一些疯狂的邪神编码,其细节使您能够从太空中看到一种新颜色。

明确地说:编码不是数据或位的属性。编码是数据解释的属性。通常只有一种正确合理对数据有意义的解释,但数据本身就是比特。

C++ 标准不强制要求 wchar_t 中存储的内容。它确实要求某些操作将 wchar_t 的内容解释为是什么。该部分描述了某些方面如何解释 wchar_t 中的数据。

【讨论】:

【参考方案6】:

您的两种解释都不正确。该标准不要求有单一的wchar_t 编码,就像它不需要单一的char 编码一样。 codecvt_utf8 facet 必须在 UTF-8 和 UCS-2 或 UCS-4 之间转换。 即使是 UTF-8、UCS-2 和 UCS-4 也不支持作为任何语言环境中的字符集。

如果Elemwchar_t 类型并且不足以存储UCS-2 值,那么codecvt_utf8 方面的转换操作是未定义的,因为标准没有说明其中会发生什么案子。如果它足够大(或者如果您想争辩说标准要求它必须足够大),那么它只是实现定义了刻面生成或使用的 UCS-2 或 UCS-4 wchar_t 值是否在编码中兼容任何语言环境定义的wchar_t 编码。

【讨论】:

我不明白他们怎么可能都是不正确的。在我看来,您的回答暗示第二个是正确的(如果不是,请指出它失败的地方)。 @n.m 您的第二个解释在两点上失败了。首先,它假设一次有一个单一的全局wchar_t 编码。有一个默认的特定于语言环境的 宽字符 编码,但这只会影响某些本地相关的库函数。其次,如果wchar_t 足够大,当Elemwchar_t 时,codecvt_utf8 方面需要在 UCS-2/4 和 UTF-8 值之间进行转换。如果 wchar_t 是 16 位,那么 convert_utf8/16 构面必须在 UCS-2 之间转换,但这并不要求使用 UCS-2。 坦率地说,我看不出第二种解释在哪里假设了类似的东西。如果在某些实现中,任何语言环境或某些已定义语言环境的默认 wchar_t 编码是 UCS4,那么显然codecvt_utf8<wchar_t> 将与该语言环境编码兼容。问题是是否需要实现才能使其正确,第二种解释说不,不是。但也许这不是最好的方式。 @n.m.您的第二种解释说,如果“wchar_t 编码不需要是 UCS2 或 UCS4”,codecvt_utf8 将不起作用。该标准不要求“wchar_t 编码”(无论您认为这意味着什么)是 UCS-2/4,但它确实需要 codecvt_ut8 才能工作。您可能会争辩说,codecvt_utf8 的要求对 wchar_t 的大小提出了要求,但它们没有对标准中其他任何地方使用的编码提出要求。 “它确实需要 codecvt_ut8 工作”也许,对于“工作”的一些定义。它不需要它明智地工作(即以与其他wchar_t功能兼容的方式;如果我转换u"abc",结果不需要等于L“abc”`在我的书中属于“不工作”)。我已经添加了我自己的答案,欢迎您发表评论。【参考方案7】:

第一个解释是有条件的。

如果定义了__STDC_ISO_10646__ 宏(从C 导入),那么wchar_t 是某个Unicode 版本的超集。

__STDC_ISO_10646__yyyymmL 形式的整数文字(例如,199712L)。如果定义了这个符号,那么每个 Unicode 要求集中的字符,当存储在wchar_t 类型的对象中时,具有相同的值 作为该字符的短标识符。所需的 Unicode 集由符合以下条件的所有字符组成 由 ISO/IEC 10646 定义,以及指定的所有修订和技术勘误 年和月。

看来,如果定义了宏,就可以假定某种 UCS4。 (不是 UCS2,因为 ISO 10646 从来没有 16 位版本;ISO 10646 的第一个版本对应于 Unicode 2.0)。

所以如果定义了宏,那么

存在“原生”wchar_t 编码 它是 UCS4 某些版本的超集 codecvt_utf8<wchar_t> 提供的转换与此原生编码兼容

如果未定义宏,则不需要保留这些内容。

还有__STDC_UTF_16____STDC_UTF_32__,但C++ 标准并没有说明它们的含义。 C 标准规定它们分别表示 char16_tchar32_t 的 UTF-16 和 UTF-32 编码,但在 C++ 中始终使用这些编码。

顺便说一句,函数mbrtoc32c32rtombchar 序列和char32_t 序列之间来回转换。在 C 中,如果定义了 __STDC_UTF_32__,它们只使用 UTF-32,但在 C++ 中,UTF-32 始终用于 char32_t。因此,即使__STDC_ISO_10646__ 未定义,它也应该可以通过从 UTF-8 转换为 UTF-32 编码的 char32_t 在 UTF-8 和 wchar_t 之间进行转换原生编码char 原生编码wchar_t,但我害怕这种复杂的东西。

【讨论】:

以上是关于C++ 标准是不是要求对 wchar_t 进行编码?的主要内容,如果未能解决你的问题,请参考以下文章

使用标准C++库,宽字符wchar如何转char递增输出?

❥关于C++之ASCII/Unicode/ISO10646及wchar_t/char16_t/char32_t

C++ WDK STL 是不是支持 wchar_t?我得到未解析的外部符号:(

通过套接字从 C++ wchar_t 到 C# char

如何在 C 中安全地声明 16 位字符串文字?

C++(真的)安全标准字符串搜索?