C大写字母怎么来的?
How does C uppercase letters?
我看到这段代码 in glibc-2.33/ctype/ctype.c
:
// [...]
#define __ctype_toupper \
((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
// [...]
int
toupper (int c)
{
return c >= -128 && c < 256 ? __ctype_toupper[c] : c;
}
libc_hidden_def (toupper)
我知道它正在检查 c
是否在 -128 和 256(含)以内,如果字符超出该范围,returns 则按原样检查字符,但是 _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
意思是我实际上在哪里可以找到字母大写的源代码?这似乎是在查找当前语言环境,我只对 en_US.UTF-8
感兴趣。还有,一个字怎么可以是负数呢?
我不关心 glibc
具体而言,我只想知道所有 ASCII 字符(从 NUL 到 DEL 的所有字符)在 C 语言中是如何大写的。
如果您返回根目录并查找 _NL_CTYPE_TOUPPER
,您会在其写入的位置找到一个提交
[..] (ctype_output): Support for alternate locale format: Computation of
nelems changes. _NL_CTYPE_TOUPPER32 [...]
所以基本上 _NL_CTYPE_TOUPPER 是 _NL_CTYPE_TOUPPER(8 位) 的宏,例如在法语中你有 À
作为 à
的大写版本
在此 link 之后,您将找到头文件 langinfo.h,该文件的枚举从第 43 行开始,_NL_CTYPE_TOUPPER
在第 259 行定义。
LC_CTYPE category: character classification.
256 This information is accessed by the functions in <ctype.h>.
LC_CTYPE 为每种语言定义,例如 French:
fr_FR:2000"
请注意,调用此函数没有多大意义,因为带重音的字符不包含在 ASCII table 中,但由于此函数是处理 utf8 和 ascii 的函数,所以它有效。
"C" 不会将字符转换为大写。 C 标准只要求标准库中有一个函数可以根据当前语言环境正确执行此操作,并且它在“C”语言环境中以特定方式执行此操作(这是唯一的语言环境 gua运行存在。
库实现可以自由地完成实现者认为合适的任务,并且它们都以不同的方式完成。甚至完全不同的方式。某些 C 库不支持除具有 ASCII 字符集的“C”语言环境之外的语言环境。这种 C 库的一个例子是 musl,它的实现非常简单:
int toupper(int c)
{
if (islower(c)) return c & 0x5f;
return c;
}
如你所见,上面的代码依赖于islower
。这是:
int islower(int c)
{
return (unsigned)c-'a' < 26;
}
因为调用了 islower
,toupper
returns 没有改变小写字符 运行ge 之外的任何参数,即使参数不在有效 运行ge 代表礼帽。由于标准没有为有效 运行ge 之外的参数定义 toupper
的行为(本质上是 fgetc
可能返回的值),因此只返回无效参数不变当然是像任何其他行为一样可以接受。 Glibc 的 toupper
函数通常会在无效参数上出现段错误,因为它使用参数作为数组的索引(正如您在引用的代码中看到的那样)。根据标准,该行为也是可以接受的。
Glibc 实现要复杂得多。在幕后,它取决于从语言环境定义文件编译的语言环境数据,这个过程完全在 C 标准之外,并且在某种程度上由 Posix 标准定义(尽管 GNU 实现在某种程度上不同于 Posix).
但这是独家新闻:如果您在 UTF-8 语言环境中使用单字节字符,glibc 的 none 复杂代码会产生细微差别。 musl 实现完全按照 UTF-8 语言环境中的要求工作,因为在单字节 UTF-8 表示中唯一可表示的字母字符是“罗马”字母表中的 52 个字符。所有其他 Unicode 字符只能在宽字符和多字节序列中表示。
此外,使用 UTF-8 以外的单字节编码的环境越来越少。我们中肯定有很多人不得不学习这些东西,因为我们的程序 运行 在使用不同 ISO-8859-x code pages 的各种平台上运行。或者不同的单字节 Windows 代码页。但最终,Unicode 赢了。 (我们中的许多人都松了一口气。)因此,除了在遗留环境中,大多数设备不再是真正必需的。
但这并不是说 Unicode 神奇地解决了管理世界上使用的大量字母表所涉及的所有复杂问题。离得很远。 Unicode 的作用有两方面:它阐明了复杂性(其中大部分未被 C/Posix 语言环境捕获),并为实现提供了一些基本标准。
而且,作为副作用,UTF-8 将单字节代码标准化,以基本符合原始 ASCII 7 位标准。因此,如果您只处理 7 位字符(如今,这可能不太理想),除了 musl 风格的实现之外,您不需要任何东西。如果你正在处理“世界上所有的字符集”,你将寻找一个实际上符合 Unicode 的库,它使用 char
以外的东西来表示字符。
但遗憾的是,一个并发症将永远存在:C 没有标准化 char
的符号性。在 char
签名的平台上(Unix X86 和 Windows,两个主要示例),
(char)0xA0
(a) 未指定并且 (b) 可能是 -96,这是单字节 0xA0 在 2 的补码中表示的内容。因此,如果您编写的代码使用 ctype.h
中的各种函数并且不处理负 char
值,然后您尝试将该代码与包含外部字符的 UTF-8 编码字符串一起使用单字节域,那么您最终会将负数传递给可能不期望它们的函数。
我看到这段代码 in glibc-2.33/ctype/ctype.c
:
// [...]
#define __ctype_toupper \
((int32_t *) _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
// [...]
int
toupper (int c)
{
return c >= -128 && c < 256 ? __ctype_toupper[c] : c;
}
libc_hidden_def (toupper)
我知道它正在检查 c
是否在 -128 和 256(含)以内,如果字符超出该范围,returns 则按原样检查字符,但是 _NL_CURRENT (LC_CTYPE, _NL_CTYPE_TOUPPER) + 128)
意思是我实际上在哪里可以找到字母大写的源代码?这似乎是在查找当前语言环境,我只对 en_US.UTF-8
感兴趣。还有,一个字怎么可以是负数呢?
我不关心 glibc
具体而言,我只想知道所有 ASCII 字符(从 NUL 到 DEL 的所有字符)在 C 语言中是如何大写的。
如果您返回根目录并查找 _NL_CTYPE_TOUPPER
,您会在其写入的位置找到一个提交
[..] (ctype_output): Support for alternate locale format: Computation of nelems changes. _NL_CTYPE_TOUPPER32 [...]
所以基本上 _NL_CTYPE_TOUPPER 是 _NL_CTYPE_TOUPPER(8 位) 的宏,例如在法语中你有 À
作为 à
在此 link 之后,您将找到头文件 langinfo.h,该文件的枚举从第 43 行开始,_NL_CTYPE_TOUPPER
在第 259 行定义。
LC_CTYPE category: character classification. 256 This information is accessed by the functions in <ctype.h>.
LC_CTYPE 为每种语言定义,例如 French:
fr_FR:2000"
请注意,调用此函数没有多大意义,因为带重音的字符不包含在 ASCII table 中,但由于此函数是处理 utf8 和 ascii 的函数,所以它有效。
"C" 不会将字符转换为大写。 C 标准只要求标准库中有一个函数可以根据当前语言环境正确执行此操作,并且它在“C”语言环境中以特定方式执行此操作(这是唯一的语言环境 gua运行存在。
库实现可以自由地完成实现者认为合适的任务,并且它们都以不同的方式完成。甚至完全不同的方式。某些 C 库不支持除具有 ASCII 字符集的“C”语言环境之外的语言环境。这种 C 库的一个例子是 musl,它的实现非常简单:
int toupper(int c)
{
if (islower(c)) return c & 0x5f;
return c;
}
如你所见,上面的代码依赖于islower
。这是:
int islower(int c)
{
return (unsigned)c-'a' < 26;
}
因为调用了 islower
,toupper
returns 没有改变小写字符 运行ge 之外的任何参数,即使参数不在有效 运行ge 代表礼帽。由于标准没有为有效 运行ge 之外的参数定义 toupper
的行为(本质上是 fgetc
可能返回的值),因此只返回无效参数不变当然是像任何其他行为一样可以接受。 Glibc 的 toupper
函数通常会在无效参数上出现段错误,因为它使用参数作为数组的索引(正如您在引用的代码中看到的那样)。根据标准,该行为也是可以接受的。
Glibc 实现要复杂得多。在幕后,它取决于从语言环境定义文件编译的语言环境数据,这个过程完全在 C 标准之外,并且在某种程度上由 Posix 标准定义(尽管 GNU 实现在某种程度上不同于 Posix).
但这是独家新闻:如果您在 UTF-8 语言环境中使用单字节字符,glibc 的 none 复杂代码会产生细微差别。 musl 实现完全按照 UTF-8 语言环境中的要求工作,因为在单字节 UTF-8 表示中唯一可表示的字母字符是“罗马”字母表中的 52 个字符。所有其他 Unicode 字符只能在宽字符和多字节序列中表示。
此外,使用 UTF-8 以外的单字节编码的环境越来越少。我们中肯定有很多人不得不学习这些东西,因为我们的程序 运行 在使用不同 ISO-8859-x code pages 的各种平台上运行。或者不同的单字节 Windows 代码页。但最终,Unicode 赢了。 (我们中的许多人都松了一口气。)因此,除了在遗留环境中,大多数设备不再是真正必需的。
但这并不是说 Unicode 神奇地解决了管理世界上使用的大量字母表所涉及的所有复杂问题。离得很远。 Unicode 的作用有两方面:它阐明了复杂性(其中大部分未被 C/Posix 语言环境捕获),并为实现提供了一些基本标准。
而且,作为副作用,UTF-8 将单字节代码标准化,以基本符合原始 ASCII 7 位标准。因此,如果您只处理 7 位字符(如今,这可能不太理想),除了 musl 风格的实现之外,您不需要任何东西。如果你正在处理“世界上所有的字符集”,你将寻找一个实际上符合 Unicode 的库,它使用 char
以外的东西来表示字符。
但遗憾的是,一个并发症将永远存在:C 没有标准化 char
的符号性。在 char
签名的平台上(Unix X86 和 Windows,两个主要示例),
(char)0xA0
(a) 未指定并且 (b) 可能是 -96,这是单字节 0xA0 在 2 的补码中表示的内容。因此,如果您编写的代码使用 ctype.h
中的各种函数并且不处理负 char
值,然后您尝试将该代码与包含外部字符的 UTF-8 编码字符串一起使用单字节域,那么您最终会将负数传递给可能不期望它们的函数。