【问题标题】:glibc's isalpha function and the en_US.UTF-8 localeglibc 的 isalpha 函数和 en_US.UTF-8 语言环境
【发布时间】:2021-04-07 10:12:27
【问题描述】:

简短版:当语言环境设置为 C 以外的其他值(换句话说,类似于 en_US.UTF-8)时,C 函数 isalpha 如何工作?

长版:在阅读大量关于 isalpha 函数的文档时,我并不是 100% 清楚其依赖于语言环境的行为是如何工作的。具体来说,我找到了文档that say things like

在某些语言环境中,可能存在 isalpha 为真的其他字符——既不是大写也不是小写的字母。但在标准的“C”语言环境中,没有这样的附加字符。

另外——如果我用一个小 C 程序测试它,我可以确认 isalpha 将返回 true/1 用于设置不同区域设置时传统 ASCII 文本范围之外的值——对于一些Unix。这个程序似乎在我基于 BSD/Darwin 的 mac 上做了一些合理的事情——但是当我在一个 ubuntu 机器上尝试它时它出现了段错误。

    #include <stdio.h>
    #include <ctype.h>
    #include <locale.h>
    #include <limits.h>
    int main() {
        setlocale(LC_ALL, "en_US.UTF-8");
        for(int i=0;i<INT_MAX;i++) {
           // printf() displays the string inside quotation
            if(isalpha(i)) {
                printf("is alpha numeric: %i\n", i);
            }

        }
       return 0;
    }

我不清楚的是 isalpha 如何知道当区域设置为 en_US.UTF-8 时哪些整数应该返回 true 以及这些整数代表什么。这只是某个范围内某个范围内的 utf 代码点的硬编码列表吗?还是不那么直接的东西?

我试着自己追这个,但我的 pigeon-c 不能胜任这项任务。

我到了ctype.cctype.h。如果我深入研究 glibc 的源代码,我会看到 isalpha 函数是 actually a macro 扩展为类似这样的东西

int isalpha (int c) {
    return __isctype (c, _ISalpha);
}

__isctypeis also a macro,所以我们将其扩展为类似

int isalpha (int c) {
    return ((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) _ISalpha) (c, _ISalpha);
}

还有_ISalpha enum expands out 到一个小端位掩码,所以现在我们正在研究这样的东西......

int isalpha (int c) {
    return ((*__ctype_b_loc ())[(int) (c)] & (unsigned short int) ((2) < 8 ? ((1 << (2)) << 8) : ((1 << (2)) >> 8))) (c, ((2) < 8 ? ((1 << (2)) << 8) : ((1 << (2)) >> 8)));
}

这就是我敲击的地方。

除了了解isalpha 的工作原理之外,我没有特别的目标。

【问题讨论】:

  • isalpha 必须在 unsigned char 值上调用。这只是未定义的行为。
  • 实际上,是的,它是一个硬编码列表 - 或者更确切地说是一个硬编码查找表。该字符用作特定于该语言环境的查找表的索引,并且相应条目的一位用于指示该字符是否为alpha。库使用内部 __ctype_b_loc() 函数来获取指向当前语言环境的正确表的指针。
  • 但请注意,将语言环境设置为 en_US.UTF-8 不会神奇地使事物变成 UTF-8。 isalpha() 继续对单字节字符进行操作。为了处理多字节字符,有一整套单独的“宽字符”函数和类型,以及相应的iswalpha() 函数。 iswalpha() 可能有一个更复杂的实现,因为查找表会非常大,但在某种程度上仍然有一个硬编码列表。
  • 因此,正如 Kamil 所说,通过在超出 unsigned char 范围的 int 上调用 isalpha()(加上 EOF),您可能会导致它在外部读取该 257 条目 (*) 查找表的边界,可能会崩溃或至少读取垃圾。
  • 谢谢@NateEldredge -- 你是否知道这些查找表最终来自哪里(在 glibc 中?在其他地方?)?

标签: c locale glibc utf


【解决方案1】:

当语言环境设置为非 C 语言(换句话说,类似 en_US.UTF-8)时,C 函数 isalpha 如何工作?

Unicode 的前 128 个字符与 ASCII 表示相同,因此没有任何变化(当 C 语言环境使用 ASCII 时)。

真正改变的是,glibc 不再使用硬编码列表,而是打开并加载语言环境文件。我相信这将来自/usr/lib/locale/locale-archive,它应该包含来自/usr/share/i18n/locales/* 文件的编译语言环境。在我的 /usr/share/i18n/locales/en_US 文件中,我看到 LC_CTYPE copy "en_GB" ,我可以转到具有 copy "i18n"en_GB,然后转到具有 copy "i18n_ctype"i18n,最后转到具有以下内容的 i18n_ctype 文件:

% The "alpha" class of the "i18n" FDCC-set is reflecting
% the recommendations in TR 10176 annex A
alpha /
   <U0041>..<U005A>;<U0061>..<U007A>;<U00AA>;<U00B5>;<U00BA>;/
   <U00C0>..<U00D6>;<U00D8>..<U00F6>;<U00F8>..<U02C1>;<U02C6>..<U02D1>;/
.... many more lines ....

我可以确认 isalpha 将为传统 ASCII 文本范围之外的值返回 true/1

来自C99 7.4p1

在所有情况下,参数都是一个 int,其值应可表示为无符号字符或应等于宏 EOF 的值。如果参数有任何其他值,则行为未定义。

循环:for(int i=0;i&lt;INT_MAX;i++) { if(isalpha(i)) { 对于任何大于UCHAR_MAXi 来说都是未定义的行为。一些程序员甚至做isalpha((unsigned char)i)。 (我记得在某些情况下,is&lt;ctype&gt;(arg) 函数参数不是无符号字符时会收到警告。

这只是某个范围内某个范围内的 utf 代码点的硬编码列表吗?还是不那么直接的东西?

是的,正如上面在/usr/share/i18n/locales/* 文件中提到的那样。

C 语言环境的硬编码列表存储在locale/C-ctype.c 中,旨在匹配POSIX

【讨论】:

  • 谢谢@KamilCuk——感谢您的彻底回答。但是,我无法理解它的一个方面(失败是我的,而不是你的)你提到 unsigned char 范围之外的任何参数都会导致未定义的行为。公平地说,某些 unix 附带了 isalpha,它使用超出此范围的数字执行操作,但这种行为不可移植,因此应该避免? (我正在考虑 BSD,根据这个兼容性部分:freebsd.org/cgi/…
  • /usr/share/i18n/locales/* 是编译语言环境描述的源文件。当您致电isalpha(或setlocale,就此而言)时,不会咨询他们。编译的语言环境文件通常在/usr/lib/locale,不一定是单独的文件(/usr/lib/locale/locale-archive 是默认存档文件)。
  • 谢谢@rici——这是有用的信息。你碰巧知道 isalpha 从什么 得到它的查找表吗?
  • @alan:行为是未定义的,如果它碰巧产生了你期望的结果,那么它仍然在“它可以做任何事情”的参数范围内。正如文档所指出的那样,即使是 BSD 实现也不能保证继续做它现在所做的事情。
  • @alan:它从编译的语言环境文件中获取它。当您调用setlocale 时,适当的编译语言环境文件将被读入内存并供后续调用使用。在 Linux 上,阅读 man 5 localeman 7 locale
猜你喜欢
  • 2017-11-15
  • 2019-09-04
  • 2011-05-18
  • 2018-09-21
  • 2018-09-21
  • 1970-01-01
  • 2019-07-31
  • 2021-06-20
相关资源
最近更新 更多