glibc 知:手册06:字符集处理
时间:2022-12-30 10:30:01
文章目录
- 1. 前言
- 2. 字符集处理
-
- 2.1. 扩展字符简介
- 2.2. 字符处理函数概述
- 2.3. 可重启的多字节转换函数
-
- 2.3.1. 选择转换及其属性
- 2.3.2. 表示转换状态
- 2.3.3. 转换单个字符
- 2.3.4. 转换多字节和宽字符串
- 2.3.5. 一个完整的多字节转换示例
- 2.4. 转换函数不能重新进入
-
- 2.4.1. 不得重新转换单个字符
- 2.4.2. 不得重新转换字符串
- 2.4.3. 不能重新进入函数中的状态
- 2.5. 通用字符集转换
-
- 2.5.1. 通用字符集转换接口
- 2.5.2. 一个完整的iconv示例
- 2.5.3. 关于其他iconv实现的一些细节
- 2.5.4. GNU C 库中的 iconv 实现
-
- 2.5.4.1. gconv-modules 文件的格式
- 2.5.4.2. 在iconv寻找转换路径
- 2.5.4.3. iconv模块数据结构
- 2.5.4.4. iconv模块接口
- 3. 参考
1. 前言
The GNU C Library Reference Manual for version 2.35
2. 字符集处理
早期使用的字符集只有每个字符 6、7 或 8 位:从不超过使用 8 位置(一个字节)表示单个字符的情况。随着越来越多的人处理非罗马字符集,这种方法的局限性变得更加明显,并非所有构成语言字符集的字符都可以使用 2^8 选项表示。本章显示添加到添加到。 C 支持多个字符集的功能。
2.1. 扩展字符简介
克服字节与字符关系的解决方案有很多 1:1 字符集和比例为 2:1 或 4:1 字符集之间的差异。本节的其余部分提供了帮助理解开发的例子 C 图书馆功能的设计决策。
我们必须立即区分内部和外部表示之间的区别。内部表示是指程序使用的表示,并将文本保存在内存中。当文本通过某种通信渠道存储或传输时,使用外部表示。外部表示的示例包括在目录中等待读取和分析的文件。
传统上,这两种表达方式之间没有区别。内外使用相同的单字节表示相同的舒适性和有用性。这种舒适性会随着字符集的增加而降低。
内部表示要克服的问题之一是处理使用不同字符集进行外部编码的文本。假设一个程序读取两个文本,并使用一些测量来比较它们。只有当文本以一般格式存储在内部时,比较才能有用。
对于这样一种常见的格式(= 字符集),八个肯定不够。所以最小的实体必须生长:现在将使用宽字符。不是每个字符都有一个字节,而是两四个字节。(三个字节在内存中很难找到,似乎没有必要超过四个字节)。
如本手册的其他部分所示,创建了一个可以处理内存中宽字符文本的新函数系列。最常用的内宽字符集是 Unicode 和 ISO 10646(也称通用字符集) UCS)。Unicode 最初计划为 16 和 ISO 10646 被设计为 31 大代码空间。这两个标准其实是一样的。它们有相同的字符库和代码表,但是 Unicode 指定附加语义。目前只分配了以前 0x10000 代码位置(所谓基本多语言平面,BMP)但是在这里 16 位置空间以外更专业字符的分配已经进行。已为 Unicode 和 ISO 10646 字符定义了许多编码:UCS-2 是一个 16 位字,只能表示 BMP 中的字符,UCS-4 是一个 32 位字,不能表示任何东西 Unicode 和 ISO 10646 字符, UTF-8 是一种 ASCII 兼容编码,其中 ASCII 字符由 ASCII 字节表示,非 ASCII 字符由 2-6 个非 ASCII 字节序列表示,最后 UTF-16 是 UCS-2 其中一些是对的UCS-2 最大编码可以使用字符 0x10ffff 的非 BMP 字符。
char 类型不适合表示宽字符。ISO C 该标准引入了一种新型号,旨在在宽字符串中保留一个字符。为了保持相似性,对于使用单个宽字符的函数,还有另一个对应 int 的类型。
数据类型:wchar_t
这种数据类型被用作宽字符串的基本类型。换句话说,这种类型的对象数组相当于多字节字符串 char[]。这种类型在 stddef.h 中定义。
引入 wchar_t 的 ISO C90 该标准没有说明相关表示的任何具体内容。它只要求这种类型存储基本字符集的所有元素。因此,它将 wchar_t 定义为 char 它是合法的,可能对嵌入式系统有意义。
但在 GNU C 库中,wchar_t 始终为 32 所以位宽可以表示一切 UCS-4 因此,它涵盖了一切 ISO 10646。一些 Unix 系统将 wchar_t 定义为 16 所以非常遵循位类型 Unicode严格。这个定义完全符合标准,但也意味着它来自 Unicode 和 ISO 10646 必须使用所有字符 UTF-16 代理字符,际上是一种多宽字符编码。但使用多宽字符编码和 wchar_t 类型目的矛盾。
数据类型:wint_t
wint_t 它是一种数据类型,用于包含单个宽字符的参数和变量。顾名思义,这种类型是普通的 char 字符串相当于 int。如果 wchar_t 和 wint_t 类型大小为 32 位宽通常有相同的表示形式,但如果是 wchar_t 定义为 char,由于参数增加,类型 wint_t 必须定义为 int。
此类型在 wchar.h 并在 ISO C90 修正案 1 中引入。
与 char 数据类型相同,宏可用于指定 wchar_t 最小值和最大值可以在类型对象中表示。
宏:wint_t WCHAR_MIN
宏 WCHAR_MIN 求值为 wint_t 对象类型的最小值可以表示。
这个宏是在 ISO C90 的修正案 1 中引入的。
宏:wint_t WCHAR_MAX
宏 WCHAR_MAX 求值为 wint_t 类型的对象可表示的最大值。
这个宏是在 ISO C90 的修正案 1 中引入的。
另一个特殊的宽字符值相当于 EOF。
宏:wint_t WEOF
宏 WEOF 计算为 wint_t 常量表达式的类型不同于任何扩展字符集的成员。
WEOF 不必与 EOF 具有相同的值,并与 EOF 不同的是,它不必是负值。换句话说,草率代码就像
{
int c; … while ((c = getc (fp)) < 0) … }
使用宽字符时,必须以显式重写 WEOF:
{
wint_t c;while ((c = getwc (fp)) != WEOF)
…
}
这个宏是在 ISO C90 修正案 1 中引入的,并在 wchar.h 中定义。
这些内部表示在存储和传输方面存在问题。因为每个单独的宽字符都包含一个以上的字节,所以它们会受到字节顺序的影响。因此,具有不同字节顺序的机器在访问相同数据时会看到不同的值。这种字节顺序问题也适用于所有基于字节的通信协议,因此要求发送者必须决定将宽字符拆分为字节。最后一点(但并非最不重要)是宽字符通常比自定义的面向字节的字符集需要更多的存储空间。
由于上述所有原因,如果后者是 UCS-2 或 UCS-4,则通常使用与内部编码不同的外部编码。外部编码是基于字节的,可以根据环境和要处理的文本适当地选择。这种外部编码可以使用多种不同的字符集(这里不会详尽地介绍信息——相反,主要组的描述就足够了)。所有基于 ASCII 的字符集都满足一个要求:它们是“文件系统安全的”。这意味着字符’/'在编码中仅用于表示它自己。对于像 EBCDIC(Extended Binary Coded Decimal Interchange Code,IBM 使用的字符集系列)这样的字符集,情况有些不同,但是如果操作系统不能直接理解 EBCDIC,那么无论如何都必须首先转换参数到系统的调用.
-
最简单的字符集是单字节字符集。最多只能有 256 个字符(对于 8 位字符集),这不足以涵盖所有语言,但可能足以处理特定文本。处理 8 位字符集很简单。这不适用于稍后介绍的其他类型,因此,一个使用的应用程序可能需要使用 8 位字符集。
-
ISO 2022 标准定义了一种扩展字符集的机制,其中一个字符可以由多个字节表示。这是通过将状态与文本相关联来实现的。可用于更改状态的字符可以嵌入到文本中。文本中的每个字节在每种状态下都可能有不同的解释。状态甚至可能会影响给定字节是单独代表一个字符,还是必须与更多字节组合。
在 ISO 2022 的大多数使用中,定义的字符集不允许状态更改超过下一个字符。这具有很大的优势,即只要可以识别字符的字节序列的开头,就可以正确解释文本。使用此策略的字符集示例包括各种 EUC 字符集(由 Sun 的操作系统、EUC-JP、EUC-KR、EUC-TW 和 EUC-CN 使用)或 Shift_JIS(SJIS,一种日语编码)。
但 也有字符集使用对多个字符有效且必须由另一个字节序列更改的状态。例如 ISO-2022-JP、ISO-2022-KR 和 ISO-2022-CN。
-
使用罗马字母为其他语言修复 8 位字符集的早期尝试导致了类似 ISO 6937 的字符集。这里表示像尖音符号这样的字符的字节本身不会产生输出:必须将它们与其他字符组合以获得所需的结果.例如,字节序列 0xc2 0x61(无间距的重音符号,后跟小写的‘a’)得到“小 a 带重音符号”。要单独获得尖音符字符,必须写入 0xc2 0x20(非间距尖音符后跟一个空格)。
诸如 ISO 6937 之类的字符集用于一些嵌入式系统,例如电传。
-
无需转换内部使用的 Unicode 或 ISO 10646 文本,通常只需使用不同于 UCS-2/UCS-4 的编码即可。Unicode 和 ISO 10646 标准甚至指定了这样的编码:UTF-8。这种编码能够表示长度为 1 到 6 的字节串中的所有 ISO 10646 31 位。
还有一些其他尝试对 ISO 10646 进行编码,例如 UTF-7,但 UTF-8 是当今唯一应该使用的编码。事实上,幸运的是,UTF-8 很快就会成为唯一必须支持的外部编码。它被证明是普遍可用的,唯一的缺点是它有利于罗马语言,因为如果为这些脚本使用特定的字符集,它会使其他脚本(西里尔文、希腊语、亚洲脚本)的字节字符串表示比必要的更长。Unicode 压缩方案等方法可以缓解这些问题。
剩下的问题是:如何选择要使用的字符集或编码。答案:你不能自己决定,它是由系统的开发者或大多数用户决定的。由于目标是互操作性,因此必须使用其他人使用的任何东西。如果没有限制,则选择基于预期用户圈的要求。换句话说,如果一个项目预计只在俄罗斯使用,那么使用 KOI8-R 或类似的字符集就可以了。但是,如果同时来自希腊的人参与其中,则应该使用允许所有人协作的字符集。
最广泛有用的解决方案似乎是:使用最通用的字符集,即 ISO 10646。使用 UTF-8 作为外部编码,用户无法充分使用自己的语言的问题已成为过去。
在这一点上,关于宽字符表示的选择的最后一条评论是必要的。我们在上面说过,自然的选择是使用 Unicode 或 ISO 10646。这不是 ISO C 标准所要求的,但至少是鼓励的。该标准至少定义了一个宏 __STDC_ISO_10646__
,该宏仅在 wchar_t 类型编码 ISO 10646 字符的系统上定义。如果未定义此符号,则应避免对宽字符表示进行假设。如果程序员只使用 C 库提供的函数来处理宽字符串,那么与其他系统的兼容性应该不会有问题。
2.2. 字符处理函数概述
Overview about Character Handling Functions
一个 Unix C 库包含两个系列中的三组不同的函数来处理字符集转换。ISO C90 标准中指定了函数系列之一(最常用的),因此即使在 Unix 世界之外也是可移植的。不幸的是,这个家庭是最没用的。应尽可能避免使用这些功能,尤其是在开发库(而不是应用程序)时。
第二个函数系列是在早期的 Unix 标准 (XPG2) 中引入的,并且仍然是最新和最伟大的 Unix 标准的一部分:Unix 98。它也是最强大和最有用的函数集。但我们将从 ISO C90 修正案 1 中定义的功能开始。
2.3. 可重启的多字节转换函数
Restartable Multibyte Conversion Functions
ISO C 标准定义了将字符串从多字节表示形式转换为宽字符串的函数。有几个特点:
- 假定用于多字节编码的字符集未指定为函数的参数。而是使用当前语言环境的 LC_CTYPE 类别指定的字符集;请参阅区域设置类别。
- 一次处理多个字符的函数需要以 NUL 结尾的字符串作为参数(即,除非可以在适当的位置添加 NUL 字节,否则无法转换文本块)。GNU C 库包含一些允许指定大小的标准扩展,但基本上它们也期望终止字符串。
尽管有这些限制,但 ISO C 函数可以在许多情况下使用。例如,在图形用户界面中,如果文本不是简单的 ASCII,具有要求文本以宽字符串显示的功能并不少见。文本本身可能来自带有翻译的文件,用户应该决定当前的语言环境,这决定了翻译,因此也决定了使用的外部编码。在这种情况下(以及许多其他情况),这里描述的功能是完美的。如果在执行转换时需要更多自由,请查看 iconv 函数(请参阅通用字符集转换)。
2.3.1. 选择转换及其属性
Selecting the conversion and its properties
我们在上面已经说过,当前为 LC_CTYPE 类别选择的语言环境决定了我们将要描述的函数执行的转换。每个语言环境都使用自己的字符集(作为 localedef 的参数给出),这是假定为外部多字节编码的字符集。GNU C 库中的宽字符集始终是 UCS-4。
每个多字节字符集的一个特征是表示一个字符可能需要的最大字节数。在编写使用转换函数的代码时,此信息非常重要(如下面的示例所示)。ISO C 标准定义了两个提供此信息的宏。
宏:int MB_LEN_MAX
MB_LEN_MAX 为任何受支持的语言环境中的单个字符指定多字节序列中的最大字节数。它是一个编译时常量,在 limits.h 中定义。
宏:int MB_CUR_MAX
MB_CUR_MAX 扩展为一个正整数表达式,它是当前语言环境中多字节字符的最大字节数。该值永远不会大于 MB_LEN_MAX。与 MB_LEN_MAX 不同,此宏不必是编译时常量,而在 GNU C 库中则不是。
MB_CUR_MAX 在 stdlib.h 中定义。
两个不同的宏是必要的,因为严格的 ISO C90 编译器不允许可变长度数组定义,但仍然希望避免动态分配。这段不完整的代码显示了问题:
{
char buf[MB_LEN_MAX];
ssize_t len = 0;
while (! feof (fp))
{
fread (&buf[len], 1, MB_CUR_MAX - len, fp);
/* … process buf */
len -= used;
}
}
预计内部循环中的代码在数组 buf 中总是有足够的字节来转换一个多字节字符。数组 buf 必须静态调整大小,因为许多编译器不允许可变大小。fread 调用确保 MB_CUR_MAX 字节在 buf 中始终可用。请注意,如果 MB_CUR_MAX 不是编译时常量,这不是问题。
2.3.2. 表示转换的状态
在本章的介绍中,有人说某些字符集使用有状态编码。也就是说,编码值在某种程度上取决于文本中的先前字节。
由于转换函数允许在多个步骤中转换文本,因此我们必须有一种方法可以将此信息从函数的一次调用传递到另一个函数调用。
数据类型:mbstate_t
mbstate_t 类型的变量可以包含有关从一个调用转换函数到另一个调用所需的转换状态的所有信息。
mbstate_t 在 wchar.h 中定义。它是在 ISO C90 的修正案 1 中引入的。
要使用 mbstate_t 类型的对象,程序员必须定义此类对象(通常作为堆栈上的局部变量)并将指向该对象的指针传递给转换函数。这样,如果当前多字节字符集是有状态的,转换函数可以更新对象。
没有特定的函数或初始化程序可以将状态对象置于任何特定状态。规则是对象应始终表示第一次使用之前的初始状态,这是通过使用如下代码清除整个变量来实现的:
{
mbstate_t state;
memset (&state, '\0', sizeof (state));
/* from now on state can be used. */
…
}
在使用转换函数生成输出时,通常需要测试当前状态是否对应于初始状态。这是必要的,例如,决定是否发出转义序列以在某些序列点将状态设置为初始状态。通信协议通常需要这样做。
函数:int mbsinit (const mbstate_t *ps)
Preliminary: | MT-Safe | AS-Safe | AC-Safe | See POSIX Safety Concepts.
mbsinit函数判断ps指向的状态对象是否处于初始状态。如果 ps 是空指针或对象处于初始状态,则返回值非零。否则为零。
mbsinit 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
使用 mbsinit 的代码通常如下所示:
{
mbstate_t state;
memset (&state, '\0', sizeof (state));
/* Use state. */
…
if (! mbsinit (&state))
{
/* Emit code to return to initial state. */
const wchar_t empty[] = L"";
const wchar_t *srcp = empty;
wcsrtombs (outbuf, &srcp, outbuflen, &state);
}
…
}
发出转义序列以返回初始状态的代码很有趣。wcsrtombs 函数可用于确定必要的输出代码(请参阅转换多字节和宽字符串)。请注意,对于 GNU C 库,不需要执行从多字节文本到宽字符文本的转换的额外操作,因为宽字符编码不是有状态的。但是在任何标准中都没有提到禁止使 wchar_t 使用有状态编码。
2.3.3. 转换单个字符
Converting Single Characters
最基本的转换函数是那些处理单个字符的函数。请注意,这并不总是意味着单个字节。但是由于多字节字符集的子集通常由单字节序列组成,因此有一些函数可以帮助转换字节。通常,ASCII 是多字节字符集的子集。在这种情况下,每个 ASCII 字符代表自己,所有其他字符至少有一个超出 0 到 127 范围的第一个字节。
函数:wint_t btowc (int c)
Preliminary: | MT-Safe | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
btowc 函数(“字节到宽字符”)使用当前选择的 LC_CTYPE 类别的语言环境中的转换规则,将处于初始移位状态的有效单字节字符 c 转换为等效的宽字符。
如果 (unsigned char) c 不是有效的单字节多字节字符或 c 是 EOF,则函数返回 WEOF。
请注意仅在初始班次状态下测试 c 有效性的限制。不使用从中获取状态信息的 mbstate_t 对象,并且该函数也不使用任何静态状态。
btowc 函数是在 ISO C90 修正案 1 中引入的,并在 wchar.h 中声明。
尽管单字节值总是在初始状态下被解释的限制,但这个函数在大多数情况下实际上是有用的。大多数字符要么完全是单字节字符集,要么是 ASCII 的扩展。但是这样写代码是可能的(并不是说这个具体的例子很有用):
wchar_t *
itow (unsigned long int val)
{
static wchar_t buf[30];
wchar_t *wcp = &buf[29];
*wcp = L'\0';
while (val != 0)
{
*--wcp = btowc ('0' + val % 10);
val /= 10;
}
if (wcp == &buf[29])
*--wcp = L'0';
return wcp;
}
为什么需要使用如此复杂的实现而不是简单地将 ‘0’ + val % 10 转换为宽字符?答案是不能保证可以对用于 wchar_t 表示的字符集的字符执行这种算术运算。在其他情况下,字节在编译时不是恒定的,因此编译器无法完成工作。在这种情况下,需要使用 btowc。
还有一个用于在另一个方向进行转换的功能。
函数:int wctob (wint_t c)
Preliminary: | MT-Safe | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
wctob 函数(“宽字符到字节”)将有效的宽字符作为参数。如果这个字符在初始状态的多字节表示正好是一个字节长,那么这个函数的返回值就是这个字符。否则返回值为 EOF。
wctob 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
有更通用的函数可以将单个字符从多字节表示转换为宽字符,反之亦然。这些函数对多字节表示的长度没有限制,也不需要它处于初始状态。
函数:size_t mbrtowc (wchar_t *restrict pwc, const char *restrict s, size_t n, mbstate_t *restrict ps)
Preliminary: | MT-Unsafe race:mbrtowc/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
mbrtowc 函数(“多字节可重启为宽字符”)将 s 指向的字符串中的下一个多字节字符转换为宽字符,并将其存储在 pwc 指向的位置。根据当前为 LC_CTYPE 类别选择的语言环境执行转换。如果语言环境中使用的字符集的转换需要状态,则多字节字符串以 ps 指向的对象表示的状态进行解释。如果 ps 是空指针,则使用仅由 mbrtowc 函数使用的静态内部状态变量。
如果下一个多字节字符对应于空宽字符,则函数的返回值为 0,然后状态对象处于初始状态。如果接下来的 n 个或更少的字节构成正确的多字节字符,则返回值是从 s 开始的构成多字节字符的字节数。转换状态根据转换中消耗的字节进行更新。在这两种情况下,如果 pwc 不为空,宽字符(L’\0’ 或在转换中找到的字符)都存储在 pwc 指向的字符串中。
如果多字节字符串的前 n 个字节可能构成一个有效的多字节字符,但完成它所需的字节数超过 n 个,则函数的返回值为 (size_t) -2 并且没有值存储在 *pwc 中。转换状态已更新,所有 n 个输入字节都已消耗,不应再次提交。请注意,即使 n 的值大于或等于 MB_CUR_MAX 也会发生这种情况,因为输入可能包含冗余移位序列。
如果多字节字符串的前 n 个字节不可能形成有效的多字节字符,则不存储任何值,将全局变量 errno 设置为值 EILSEQ,并且函数返回 (size_t) -1。转换状态之后是未定义的。
如指定的那样,mbrtowc 函数可以处理包含嵌入空字节的多字节序列(这发生在 Unicode 编码中,例如 UTF-16),但 GNU C 库不支持这种多字节编码。当遇到空输入字节时,该函数将返回零,或返回 (size_t) -1) 并报告 EILSEQ 错误。iconv 函数可用于任意编码之间的转换。请参阅通用字符集转换接口。
mbrtowc 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
将多字节字符串复制为宽字符串同时将所有小写字符转换为大写的函数可能如下所示:
wchar_t *
mbstouwcs (const char *s)
{
/* Include the null terminator in the conversion. */
size_t len = strlen (s) + 1;
wchar_t *result = reallocarray (NULL, len, sizeof (wchar_t));
if (result == NULL)
return NULL;
wchar_t *wcp = result;
mbstate_t state;
memset (&state, '\0', sizeof (state));
while (true)
{
wchar_t wc;
size_t nbytes = mbrtowc (&wc, s, len, &state);
if (nbytes == 0)
{
/* Terminate the result string. */
*wcp = L'\0';
break;
}
else if (nbytes == (size_t) -2)
{
/* Truncated input string. */
errno = EILSEQ;
free (result);
return NULL;
}
else if (nbytes == (size_t) -1)
{
/* Some other error (including EILSEQ). */
free (result);
return NULL;
}
else
{
/* A character was converted. */
*wcp++ = towupper (wc);
len -= nbytes;
s += nbytes;
}
}
return result;
}
在内部循环中,单个宽字符存储在 wc 中,消耗的字节数存储在变量 nbytes 中。如果转换成功,则将宽字符的大写变体存储在结果数组中,并调整指向输入字符串的指针和可用字节数。如果 mbrtowc 函数返回零,则表示空输入字节尚未转换,因此必须将其显式存储在结果中。
上面的代码使用了这样一个事实,即转换结果中的宽字符永远不会超过多字节输入字符串中的字节数。这种方法对结果的大小产生了悲观的猜测,如果必须以这种方式构造许多宽字符串或者如果字符串很长,则由于输入字符串包含多字节字符而需要分配的额外内存可能很重要。分配的内存块可以在返回之前调整为正确的大小,但更好的解决方案可能是立即为结果分配适量的空间。不幸的是,没有直接从多字节字符串计算宽字符串长度的函数。但是,有一个功能可以完成部分工作。
函数:size_t mbrlen (const char *restrict s, size_t n, mbstate_t *ps)
Preliminary: | MT-Unsafe race:mbrlen/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
mbrlen 函数(“多字节可重新启动长度”)计算从 s 开始的最多 n 个字节的数量,这些字节构成下一个有效且完整的多字节字符。
如果下一个多字节字符对应 NUL 宽字符,则返回值为 0。如果接下来的 n 个字节构成有效的多字节字符,则返回属于该多字节字符字节序列的字节数。
如果前 n 个字节可能构成一个有效的多字节字符但该字符不完整,则返回值为 (size_t) -2。否则多字节字符序列无效,返回值为(size_t) -1。
多字节序列以 ps 指向的对象所代表的状态进行解释。如果 ps 是空指针,则使用 mbrlen 本地的状态对象。
mbrlen 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
细心的读者现在会注意到 mbrlen 可以实现为
mbrtowc (NULL, s, n, ps != NULL ? ps : &internal)
这是真的,事实上官方规范中也提到了。如何使用此函数确定从多字节字符串创建的宽字符串的长度?它不能直接使用,但我们可以使用它定义一个函数 mbslen:
size_t
mbslen (const char *s)
{
mbstate_t state;
size_t result = 0;
size_t nbytes;
memset (&state, '\0', sizeof (state));
while ((nbytes = mbrlen (s, MB_LEN_MAX, &state)) > 0)
{
if (nbytes >= (size_t) -2)
/* Something is wrong. */
return (size_t) -1;
s += nbytes;
++result;
}
return result;
}
该函数只为字符串中的每个多字节字符调用 mbrlen 并计算函数调用的次数。请注意,我们在这里使用 MB_LEN_MAX 作为 mbrlen 调用中的大小参数。这是可以接受的,因为 a) 这个值大于最长的多字节字符序列的长度 b) 我们知道字符串 s 以 NUL 字节结尾,它不能是任何其他多字节字符序列的一部分,但代表 NUL 的字符序列宽字符。因此,mbrlen 函数永远不会读取无效内存。
现在这个函数可用了(只是为了说明这一点,这个函数不是 GNU C 库的一部分)我们可以计算存储转换后的多字节字符串 s 所需的宽字符数
wcs_bytes = (mbslen (s) + 1) * sizeof (wchar_t);
请注意,mbslen 函数效率很低。使用 mbslen 实现 mbstouwcs 必须执行两次多字节字符输入字符串的转换,而且这种转换可能非常昂贵。所以有必要在做两次工作之前考虑使用更简单但不精确的方法的后果。
函数:size_t wcrtomb (char *restrict s, wchar_t wc, mbstate_t *restrict ps)
Preliminary: | MT-Unsafe race:wcrtomb/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
wcrtomb 函数(“可重新启动为多字节的宽字符”)将单个宽字符转换为对应于该宽字符的多字节字符串。
如果 s 是空指针,则该函数将存储在 ps 指向的对象(或内部 mbstate_t 对象)中的状态重置为初始状态。这也可以通过这样的调用来实现:
wcrtombs (temp_buf, L'\0', ps)
因为,如果 s 是一个空指针,wcrtomb 就像它写入一个内部缓冲区一样执行,该缓冲区保证足够大。
如果 wc 是 NUL 宽字符,则 wcrtomb 会在必要时发出一个移位序列,以使状态 ps 进入初始状态,然后是单个 NUL 字节,该字节存储在字符串 s 中。
否则,将一个字节序列(可能包括移位序列)写入字符串 s。仅当 wc 是有效的宽字符时才会发生这种情况(即,它在 LC_CTYPE 类别的语言环境选择的字符集中具有多字节表示)。如果 wc 不是有效的宽字符,则字符串 s 中不存储任何内容,errno 设置为 EILSEQ,ps 中的转换状态未定义,返回值为 (size_t) -1。
如果没有发生错误,函数返回存储在字符串 s 中的字节数。这包括表示移位序列的所有字节。
关于函数接口的一句话:没有参数指定数组s的长度。相反,该函数假定至少有 MB_CUR_MAX 字节可用,因为这是表示单个字符的任何字节序列的最大长度。所以调用者必须确保有足够的可用空间,否则会发生缓冲区溢出。
wcrtomb 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
使用 wcrtomb 就像使用 mbrtowc 一样简单。以下示例将宽字符串附加到多字节字符串。同样,代码并不是真正有用(或正确),它只是在这里演示使用和一些问题。
char *
mbscatwcs (char *s, size_t len, const wchar_t *ws)
{
mbstate_t state;
/* Find the end of the existing string. */
char *wp = strchr (s, '\0');
len -= wp - s;
memset (&state, '\0', sizeof (state));
do
{
size_t nbytes;
if (len < MB_CUR_LEN)
{
/* We cannot guarantee that the next character fits into the buffer, so return an error. */
errno = E2BIG;
return NULL;
}
nbytes = wcrtomb (wp, *ws, &state);
if (nbytes == (size_t) -1)
/* Error in the conversion. */
return NULL;
len -= nbytes;
wp += nbytes;
}
while (*ws++ != L'\0');
return s;
}
首先,该函数必须找到当前在数组 s 中的字符串的结尾。strchr 调用非常有效地执行此操作,因为对多字节字符表示的要求是 NUL 字节除了表示自身(在此上下文中为字符串的结尾)外,永远不会被使用。
初始化状态对象后,进入循环,第一个任务是确保数组 s 中有足够的空间。如果至少没有可用的 MB_CUR_LEN 字节,我们将中止。这并不总是最优的,但我们别无选择。我们可用的字节数可能少于 MB_CUR_LEN,但下一个多字节字符也可能只有一个字节长。在 wcrtomb 调用返回时,决定缓冲区是否足够大已经太晚了。如果此解决方案不合适,则有一个非常缓慢但更准确的解决方案。
…
if (len < MB_CUR_LEN)
{
mbstate_t temp_state;
memcpy (&temp_state, &state, sizeof (state));
if (wcrtomb (NULL, *ws, &temp_state) > len)
{
/* We cannot guarantee that the next character fits into the buffer, so return an error. */
errno = E2BIG;
return NULL;
}
}
…
在这里,我们执行可能会溢出缓冲区的转换,以便我们之后可以对缓冲区大小做出准确的决定。请注意新 wcrtomb 调用中目标缓冲区的 NULL 参数; 因为此时我们对转换后的文本不感兴趣,所以这是表达这一点的好方法。这段代码最不寻常的地方当然是转换状态对象的重复,但是如果需要更改状态以发出下一个多字节字符,我们希望在实际转换中执行相同的转换状态更改。因此,我们必须保留初始移位状态信息。
对于这个问题,肯定有更多甚至更好的解决方案。此示例仅用于教育目的。
2.3.4. 转换多字节和宽字符串
Converting Multibyte and Wide Character Strings
上一节中描述的函数一次只转换一个字符。在实际程序中执行的大多数操作都包括字符串,因此 ISO C 标准还定义了对整个字符串的转换。但是,定义的功能集非常有限;因此,GNU C 库包含一些可以在某些重要情况下提供帮助的扩展。
函数:size_t mbsrtowcs (wchar_t *restrict dst, const char **restrict src, size_t len, mbstate_t *restrict ps)
Preliminary: | MT-Unsafe race:mbsrtowcs/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.。
mbsrtowcs 函数(“多字节字符串可重新启动为宽字符串”)将 *src 处的以 NUL 结尾的多字节字符串转换为等效的宽字符串,包括末尾的 NUL 宽字符。如果 ps 是空指针,则使用来自 ps 指向的对象或来自 mbsrtowcs 的内部对象的状态信息开始转换。在返回之前,状态对象被更新以匹配最后一个转换字符之后的状态。如果到达并转换了终止 NUL 字节,则该状态是初始状态。
如果dst不是空指针,则结果存放在dst指向的数组中;否则,转换结果不可用,因为它存储在内部缓冲区中。
如果 len 宽字符在到达输入字符串末尾之前存储在数组 dst 中,则转换停止并返回 len。如果 dst 是空指针,则永远不会检查 len。
函数调用过早返回的另一个原因是输入字符串是否包含无效的多字节序列。在这种情况下,全局变量 errno 设置为 EILSEQ 并且函数返回 (size_t) -1。
在所有其他情况下,该函数返回在此调用期间转换的宽字符数。如果 dst 不为空,则 mbsrtowcs 在 src 指向的指针中存储空指针(如果已到达输入字符串中的 NUL 字节)或最后一个转换的多字节字符之后的字节地址。
与 mbstowcs 一样,dst 参数可能是一个空指针,并且该函数可用于计算所需的宽字符数。
mbsrtowcs 在 ISO C90 的修正案 1 中引入,并在 wchar.h 中声明。
mbsrtowcs 函数的定义有一个重要的限制。如果想要用文本转换缓冲区,则 dst 必须是一个以 NUL 结尾的字符串的要求会带来问题。缓冲区通常不是以 NUL 结尾的字符串的集合,而是由换行符分隔的连续行集合。现在假设需要一个从缓冲区转换一行的函数。由于该行不是 NUL 终止的,源指针不能直接指向未修改的文本缓冲区。这意味着,要么在 mbsrtowcs 函数调用时将 NUL 字节插入适当的位置(这对于只读缓冲区或多线程应用程序是不可行的),要么将行复制到额外的缓冲区中它可以由 NUL 字节终止。请注意,通常不可能通过将参数 len 设置为任何特定值来限制要转换的字符数。由于不知道每个多字节字符序列的长度是多少字节,因此只能猜测。
在换行符之后 NUL 终止一行的方法仍然存在问题,这可能会导致非常奇怪的结果。正如上面对mbsrtowcs函数的描述中所说,在处理输入字符串末尾的NUL字节后,保证转换状态处于初始移位状态。但是这个 NUL 字节实际上并不是文本的一部分(即,原始文本中换行符之后的转换状态可能与初始移位状态不同,因此下一行的第一个字符使用这种状态进行编码)。但是用户永远无法访问有问题的状态,因为转换在 NUL 字节(重置状态)之后停止。今天使用的大多数有状态字符集都要求换行符后的移位状态是初始状态——但这并不是一个严格的保证。因此,简单地以 NUL 终止一段正在运行的文本并不总是一个适当的解决方案,因此永远不应该在一般使用的代码中使用。
通用转换接口(请参阅通用字符集转换)没有此限制(它仅适用于缓冲区,而不是字符串),并且 GNU C 库包含一组函数,这些函数采用附加参数指定从输入字符串。这样,上面 mbsrtowcs 示例的问题可以通过确定行长度并将此长度传递给函数来解决。
函数:size_t wcsrtombs (char *restrict dst, const wchar_t **restrict src, size_t len, mbstate_t *restrict ps)
Preliminary: | MT-Unsafe race:wcsrtombs/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
wcsrtombs 函数(“宽字符串可重新启动为多字节字符串”)将 *src 处的以 NUL 结尾的宽字符串转换为等效的多字节字符串,并将结果存储在 dst 指向的数组中。NUL 宽字符也被转换。如果 ps 是空指针,则转换从 ps 指向的对象或 wcsrtombs 本地的状态对象中描述的状态开始。如果 dst 是空指针,则转换照常执行,但结果不可用。如果输入字符串的所有字符都已成功转换并且 dst 不是空指针,则 src 指向的指针被分配一个空指针。
如果输入字符串中的宽字符之一没有等效的有效多字节字符,则转换提前停止,将全局变量 errno 设置为 EILSEQ,并返回 (size_t) -1。
过早停止的另一个原因是,如果 dst 不是空指针,并且下一个转换的字符对数组 dst 总共需要超过 len 个字节。在这种情况下(如果 dst 不是空指针),src 指向的指针在最后一个成功转换后立即被分配一个指向宽字符的值。
除了编码错误的情况,wcsrtombs 函数的返回值是所有多字节字符序列中的字节数,这些字符序列已经或将会(如果 dst 不是空值)存储在 dst 中。在返回之前,ps 所指向的对象(如果 ps 为空指针,则为内部对象)中的状态会被更新以反映上次转换后的状态。在终止 NUL 宽字符被转换的情况下,该状态是初始移位状态。
wcsrtombs 函数是在 ISO C90 修正案 1 中引入的,并在 wchar.h 中声明。
上面提到的 mbsrtowcs 函数的限制也适用于此。无法直接控制输入字符的数量。必须将 NUL 宽字符放在正确的位置,或者通过可用的输出数组大小(len 参数)间接控制消耗的输入。
函数:size_t mbsnrtowcs (wchar_t *restrict dst, const char **restrict src, size_t nmc, size_t len, mbstate_t *restrict ps)
Preliminary: | MT-Unsafe race:mbsnrtowcs/!ps | AS-Unsafe corrupt heap lock dlopen | AC-Unsafe corrupt lock mem fd | See POSIX Safety Concepts.
mbsnrtowcs 函数与 mbsrtowcs 函数非常相似。除了 nmc 是新的之外,所有参数都相同。返回值与 mbsrtowcs 相同。
这个新参数指定了多字节字符串中最多可以使用多少字节。换句话说,多字节字符串 *src 不需要以 NUL 结尾。但是如果在字符串的 nmc 第一个字节中找到 NUL 字节,则转换将停止。
与 mbstowcs 一样,dst 参数可能是一个空指针,并且该函数可用于计算所需的宽字符数。
这个函数是一个 GNU 扩展。它旨在解决上述问题。现在可以逐段转换具有多字节字符文本的缓冲区,而不必关心插入 NUL 字节以及 NUL 字节对转换状态的影响。
将多字节字符串转换为宽字符串并显示它的函数可以这样编写(这不是一个真正有用的示例):
void showmbs (const char *src, FILE *fp) { mbstate_t state; int cnt = 0; memset (&state, '\0', sizeof 元器件数据手册
、IC替代型号,打造电子元器件IC百科大全!