提问人:Carlo Arenas 提问时间:10/23/2023 最后编辑:Carlo Arenas 更新时间:10/24/2023 访问量:170
如何处理 toupper() 在具有 UTF-8 语言环境的最新 macOS 中返回的大于 255 的值
How to handle values greater than 255 as returned by toupper() in recent macOS with UTF-8 locales
问:
下面的代码试图解决的问题是如何有效地检测基于 UTF-8 的语言环境可能正在使用中,以便不会查询 127 以上的所有代码点的属性,因为我们正在处理普通(不是宽)字符。ctype
至少在 macOS 14 中,使用基于 UTF-8 的区域设置时。以下程序将显示 2 个有问题的代码点,即使这些代码点对无符号字符有效,也会从无法适合该类型的值中获取响应:toupper()
#include <langinfo.h>
#include <ctype.h>
#include <locale.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef WORKAROUND
static int is_utf8_locale(void)
{
const char *charmap = nl_langinfo(CODESET);
/* this shouldn't happen */
if (!charmap)
return 0;
if (!strncmp(charmap, "UTF-8", 5))
return 1;
/*
* nl_langinfo should never return an empty string, unless the "item" used is invalid, and it
* should return the C/POSIX CODESET if the locale is missing one, but ...
*/
if (!*charmap) {
unsigned char buf[MB_CUR_MAX + 1];
return (wctomb((char *)&buf, 0xf8ff) == 3) &&
(buf[0] == 0xef && buf[1] == 0xa3 && buf[2] == 0xbf);
}
return 0;
}
#endif
int main(int argc, char *argv[])
{
const char *locale = (argc > 1) ? argv[1] : "fr_FR";
int i, f = 0;
if (!setlocale(LC_CTYPE, locale))
return 127;
#ifdef WORKAROUND
if (is_utf8_locale())
return 0;
#endif
for (i = 128; i < 256; i++) {
int u, l;
unsigned char c = i;
l = tolower(c);
u = toupper(c);
if (l > 255) {
int t = l % 256;
f++;
printf("tolower(%d) %c -> %d (%#x) %c\n", c, c, l, t, t);
}
if (u > 255) {
int t = u % 256;
f++;
printf("toupper(%d) %c -> %d (%#x) %c\n", c, c, u, t, t);
}
}
return f;
}
默认fr_FR区域设置的输出(经过一些次要格式化后)显示:
toupper(181) µ -> 924 (0x9c) <9c>
toupper(255) ÿ -> 376 (0x78) x
AFAIK,这种行为变化在某种程度上是最近的,至少不会发生在 10.15 中,虽然众所周知“有时”会尝试更有帮助(作为 BSD 扩展),但我在我尝试过的任何最近 BSD 系统中都看不到这个问题,他们都提到该行为已被弃用,建议改用宽字符接口。toupper()
toupper()
“-DWORKAROUND”可以工作,但恕我直言,它太丑陋了,而且在线程环境中也会有问题,同时由于 macOS 定义其语言环境的方式而特别棘手。
所有没有显式语言环境(显示注释中描述的问题响应)以及包含和有时在其他系统中使用的语言环境(尽管在这些情况下它们总是返回正确的值)似乎都受到影响。.${CHARMAP}
nl_langinfo()
.UTF-8
.utf8
nl_langinfo()
该解决方法显然需要非 POSIX 系统的额外代码。
受影响的应用程序不支持 UTF-8 以外的外来多字节编码,但使用的检测很脆弱,可能无法在 Apple 系统之外工作,因此也希望提供建议或测试结果。wctomb()
答:
如何处理 toupper() 在具有 UTF-8 语言环境的最新 macOS 中返回的大于 255 的值
在任何区域设置和任何系统中,都不可能使用(在具有正常 8 位字符的健全系统上)处理大于 255 的值。toupper
评论
char
toupper
处理大于 255 的值,这当然会在具有 8 位字节的系统上调用未定义的行为,而在于如何处理 toupper()
返回的大于 255 的值,这似乎是 macOS C 库中的一个错误。
C 标准没有明确规定 返回的值必须在 type 或特殊值的范围内,但下面的段落似乎暗示它们应该:toupper()
unsigned char
EOF
7.4 字符处理
<ctype.h>
标头声明了几个可用于分类和映射字符的函数。在所有情况下,参数都是 ,其值应表示为 或 等于宏的值。如果参数具有任何其他值,则行为未定义。
<ctype.h>
int
unsigned char
EOF
[...]
7.4.2.2
toupper
函数概要
#include <ctype.h> int toupper(int c);
说明
该函数将小写字母转换为相应的大写字母。toupper
返回
如果参数是 true 的字符,并且有一个或多个相应的字符(由当前区域设置指定)为 true,则该函数返回一个相应的字符(对于任何给定的区域设置始终是相同的字符);否则,将返回参数不变。islower
isupper
toupper
这意味着要么toupper(181)
- 返回一个字符,其返回非零
isupper
- 或返回 181
如果返回的值大于定义范围内的参数,则这是一个错误,确实可能会在许多程序中导致进一步的问题,例如:https://github.com/PCRE2Project/pcre2/pull/313toupper
UCHAR_MAX
我的系统(macOS 13.4,Homebrew clang 版本 16.0.6,目标:x86_64-apple-darwin22.5.0)上存在问题,如下面的测试程序所示:
#include <ctype.h>
#include <langinfo.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
const char *locale = (argc > 1) ? argv[1] : "fr_FR";
int f = 0;
if (!setlocale(LC_CTYPE, locale))
return 127;
printf("Testing locale %s:\n", locale);
for (int c = 128; c < 256; c++) {
int l = tolower(c);
int u = toupper(c);
if (l > 255) {
char cbuf[MB_CUR_MAX];
char lbuf[MB_CUR_MAX];
cbuf[wctomb(cbuf, c)] = '\0';
lbuf[wctomb(lbuf, l)] = '\0';
printf("%d: %s isupper(%d): %d tolower(%d): %d, %s\n",
c, cbuf, c, isupper(c), c, l, lbuf);
f++;
}
if (u > 255) {
char cbuf[MB_CUR_MAX];
char ubuf[MB_CUR_MAX];
cbuf[wctomb(cbuf, c)] = '\0';
ubuf[wctomb(ubuf, u)] = '\0';
printf("%d: %s islower(%d): %d toupper(%d): %d, %s\n",
c, cbuf, c, islower(c), c, u, ubuf);
f++;
}
}
if (f) {
printf("%d errors!\n", f);
}
return f;
}
我的系统中的输出:
Testing locale fr_FR:
181: µ islower(181): 1 toupper(181): 924, Μ
255: ÿ islower(255): 1 toupper(255): 376, Ÿ
2 errors!
查看 Apple LibC 的源代码,似乎他们试图对宏和宽字符版本使用相同的表,并且仅拒绝大于 .碰巧的是,Unicode 中的大写版本大于 并且应该返回此区域设置,但应该被 忽略。这是实现中的一个错误。<ctype.h>
towupper
towlower
UCHAR_MAX
µ
ÿ
UCHAR_MAX
towupper
toupper
评论
toupper
192
À
tolower(192)
224
à
toupper(181)
getchar()
isxxx()
0
tolower()
toupper()
该问题仅存在于和/或好友损坏(不符合 C 标准)的系统上。因此,没有必要检测 UTF-8 语言环境,需要检测损坏的区域设置。toupper
toupper
如果为任何输入返回一个值 >255,则它(和整个系列)被破坏(AFAICT 不仅针对该输入,而且至少针对所有输入 >127)。因此,如果检测到此类值,请立即停在那里并重新初始化表的整个上半部分。toupper
ctype
您甚至可以从检查开始。如果该值大于 255,则表示已损坏。此方法仅检测此特定错误,但您不能希望检测到所有可能的错误。检测并解决您知道的人。toupper(181)
ctype
libc
使用从 @chqrlie 提出的代码派生的调试工具,并验证了 macOS 13 中也发生的虚假响应,并且可能是由于该函数的实现错误,似乎最好的选择是做一个狭窄的解决方法,忽略虚假值并使函数以某种方式更接近标准, 已经实施。isupper()
代码:
#include <langinfo.h>
#include <ctype.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
const char *locale = (argc > 1) ? argv[1] : "fr_FR";
int verbose = (argc > 2);
int f = 0, c;
if (!setlocale(LC_CTYPE, locale)) {
printf("usage: %s [<locale>] [-v]\n", argv[0]);
printf("\tlocale defaults to fr_FR\n");
printf("\t-v prints all characters with mappings\n");
return 127;
}
printf("Testing locale %s with charmap \"%s\":\n", locale, nl_langinfo(CODESET));
for (c = 128; c < 256; c++) {
char cbuf[MB_CUR_MAX];
char buf[MB_CUR_MAX];
int bug = 0;
int l = tolower(c);
int u = toupper(c);
cbuf[wctomb(cbuf, c)] = '\0';
if (l > 255) {
buf[wctomb(buf, l)] = '\0';
printf("%x: %s isupper(%d): %d tolower(%d): %d, %s (tolower bug)\n",
c, cbuf, c, isupper(c), c, l, buf);
bug++;
}
if (u > 255) {
buf[wctomb(buf, u)] = '\0';
printf("%x: %s islower(%d): %d toupper(%d): %d, %s (toupper bug)\n",
c, cbuf, c, islower(c), c, u, buf);
bug++;
}
if (verbose && !bug) {
if (l != c) {
buf[wctomb(buf, l)] = '\0';
printf("%x: %s isupper(%d): %d tolower(%d): %d, %s\n",
c, cbuf, c, isupper(c), c, l, buf);
}
if (u != c) {
buf[wctomb(buf, u)] = '\0';
printf("%x: %s islower(%d): %d toupper(%d): %d, %s\n",
c, cbuf, c, islower(c), c, u, buf);
}
}
if (bug) {
f += bug;
bug = 0;
}
}
if (f)
printf("%d errors!\n", f);
return f;
}
在 macOS 14 中,对于 3 个法语区域设置,输出如下(默认输出与 相同,但原始代码中描述的“未指定”字符映射除外)fr_FR.UTF-8
% ./x fr_FR.UTF-8 -v
Testing locale fr_FR.UTF-8 with charmap "UTF-8":
b5: µ islower(181): 1 toupper(181): 924, Μ (toupper bug)
c0: À isupper(192): 1 tolower(192): 224, à
c1: Á isupper(193): 1 tolower(193): 225, á
c2: Â isupper(194): 1 tolower(194): 226, â
c3: Ã isupper(195): 1 tolower(195): 227, ã
c4: Ä isupper(196): 1 tolower(196): 228, ä
c5: Å isupper(197): 1 tolower(197): 229, å
c6: Æ isupper(198): 1 tolower(198): 230, æ
c7: Ç isupper(199): 1 tolower(199): 231, ç
c8: È isupper(200): 1 tolower(200): 232, è
c9: É isupper(201): 1 tolower(201): 233, é
ca: Ê isupper(202): 1 tolower(202): 234, ê
cb: Ë isupper(203): 1 tolower(203): 235, ë
cc: Ì isupper(204): 1 tolower(204): 236, ì
cd: Í isupper(205): 1 tolower(205): 237, í
ce: Î isupper(206): 1 tolower(206): 238, î
cf: Ï isupper(207): 1 tolower(207): 239, ï
d0: Ð isupper(208): 1 tolower(208): 240, ð
d1: Ñ isupper(209): 1 tolower(209): 241, ñ
d2: Ò isupper(210): 1 tolower(210): 242, ò
d3: Ó isupper(211): 1 tolower(211): 243, ó
d4: Ô isupper(212): 1 tolower(212): 244, ô
d5: Õ isupper(213): 1 tolower(213): 245, õ
d6: Ö isupper(214): 1 tolower(214): 246, ö
d8: Ø isupper(216): 1 tolower(216): 248, ø
d9: Ù isupper(217): 1 tolower(217): 249, ù
da: Ú isupper(218): 1 tolower(218): 250, ú
db: Û isupper(219): 1 tolower(219): 251, û
dc: Ü isupper(220): 1 tolower(220): 252, ü
dd: Ý isupper(221): 1 tolower(221): 253, ý
de: Þ isupper(222): 1 tolower(222): 254, þ
e0: à islower(224): 1 toupper(224): 192, À
e1: á islower(225): 1 toupper(225): 193, Á
e2: â islower(226): 1 toupper(226): 194, Â
e3: ã islower(227): 1 toupper(227): 195, Ã
e4: ä islower(228): 1 toupper(228): 196, Ä
e5: å islower(229): 1 toupper(229): 197, Å
e6: æ islower(230): 1 toupper(230): 198, Æ
e7: ç islower(231): 1 toupper(231): 199, Ç
e8: è islower(232): 1 toupper(232): 200, È
e9: é islower(233): 1 toupper(233): 201, É
ea: ê islower(234): 1 toupper(234): 202, Ê
eb: ë islower(235): 1 toupper(235): 203, Ë
ec: ì islower(236): 1 toupper(236): 204, Ì
ed: í islower(237): 1 toupper(237): 205, Í
ee: î islower(238): 1 toupper(238): 206, Î
ef: ï islower(239): 1 toupper(239): 207, Ï
f0: ð islower(240): 1 toupper(240): 208, Ð
f1: ñ islower(241): 1 toupper(241): 209, Ñ
f2: ò islower(242): 1 toupper(242): 210, Ò
f3: ó islower(243): 1 toupper(243): 211, Ó
f4: ô islower(244): 1 toupper(244): 212, Ô
f5: õ islower(245): 1 toupper(245): 213, Õ
f6: ö islower(246): 1 toupper(246): 214, Ö
f8: ø islower(248): 1 toupper(248): 216, Ø
f9: ù islower(249): 1 toupper(249): 217, Ù
fa: ú islower(250): 1 toupper(250): 218, Ú
fb: û islower(251): 1 toupper(251): 219, Û
fc: ü islower(252): 1 toupper(252): 220, Ü
fd: ý islower(253): 1 toupper(253): 221, Ý
fe: þ islower(254): 1 toupper(254): 222, Þ
ff: ÿ islower(255): 1 toupper(255): 376, Ÿ (toupper bug)
2 errors!
% ./x fr_FR.ISO8859-1 -v | iconv -f LATIN1 -t UTF-8
Testing locale fr_FR.ISO8859-1 with charmap "ISO8859-1":
c0: À isupper(192): 1 tolower(192): 224, à
c1: Á isupper(193): 1 tolower(193): 225, á
c2: Â isupper(194): 1 tolower(194): 226, â
c3: Ã isupper(195): 1 tolower(195): 227, ã
c4: Ä isupper(196): 1 tolower(196): 228, ä
c5: Å isupper(197): 1 tolower(197): 229, å
c6: Æ isupper(198): 1 tolower(198): 230, æ
c7: Ç isupper(199): 1 tolower(199): 231, ç
c8: È isupper(200): 1 tolower(200): 232, è
c9: É isupper(201): 1 tolower(201): 233, é
ca: Ê isupper(202): 1 tolower(202): 234, ê
cb: Ë isupper(203): 1 tolower(203): 235, ë
cc: Ì isupper(204): 1 tolower(204): 236, ì
cd: Í isupper(205): 1 tolower(205): 237, í
ce: Î isupper(206): 1 tolower(206): 238, î
cf: Ï isupper(207): 1 tolower(207): 239, ï
d0: Ð isupper(208): 1 tolower(208): 240, ð
d1: Ñ isupper(209): 1 tolower(209): 241, ñ
d2: Ò isupper(210): 1 tolower(210): 242, ò
d3: Ó isupper(211): 1 tolower(211): 243, ó
d4: Ô isupper(212): 1 tolower(212): 244, ô
d5: Õ isupper(213): 1 tolower(213): 245, õ
d6: Ö isupper(214): 1 tolower(214): 246, ö
d8: Ø isupper(216): 1 tolower(216): 248, ø
d9: Ù isupper(217): 1 tolower(217): 249, ù
da: Ú isupper(218): 1 tolower(218): 250, ú
db: Û isupper(219): 1 tolower(219): 251, û
dc: Ü isupper(220): 1 tolower(220): 252, ü
dd: Ý isupper(221): 1 tolower(221): 253, ý
de: Þ isupper(222): 1 tolower(222): 254, þ
e0: à islower(224): 1 toupper(224): 192, À
e1: á islower(225): 1 toupper(225): 193, Á
e2: â islower(226): 1 toupper(226): 194, Â
e3: ã islower(227): 1 toupper(227): 195, Ã
e4: ä islower(228): 1 toupper(228): 196, Ä
e5: å islower(229): 1 toupper(229): 197, Å
e6: æ islower(230): 1 toupper(230): 198, Æ
e7: ç islower(231): 1 toupper(231): 199, Ç
e8: è islower(232): 1 toupper(232): 200, È
e9: é islower(233): 1 toupper(233): 201, É
ea: ê islower(234): 1 toupper(234): 202, Ê
eb: ë islower(235): 1 toupper(235): 203, Ë
ec: ì islower(236): 1 toupper(236): 204, Ì
ed: í islower(237): 1 toupper(237): 205, Í
ee: î islower(238): 1 toupper(238): 206, Î
ef: ï islower(239): 1 toupper(239): 207, Ï
f0: ð islower(240): 1 toupper(240): 208, Ð
f1: ñ islower(241): 1 toupper(241): 209, Ñ
f2: ò islower(242): 1 toupper(242): 210, Ò
f3: ó islower(243): 1 toupper(243): 211, Ó
f4: ô islower(244): 1 toupper(244): 212, Ô
f5: õ islower(245): 1 toupper(245): 213, Õ
f6: ö islower(246): 1 toupper(246): 214, Ö
f8: ø islower(248): 1 toupper(248): 216, Ø
f9: ù islower(249): 1 toupper(249): 217, Ù
fa: ú islower(250): 1 toupper(250): 218, Ú
fb: û islower(251): 1 toupper(251): 219, Û
fc: ü islower(252): 1 toupper(252): 220, Ü
fd: ý islower(253): 1 toupper(253): 221, Ý
fe: þ islower(254): 1 toupper(254): 222, Þ
% ./x fr_FR.ISO8859-15 -v | iconv -f ISO8859-15 -t UTF-8
Testing locale fr_FR.ISO8859-15 with charmap "ISO8859-15":
a6: Š isupper(166): 1 tolower(166): 168, š
a8: š islower(168): 1 toupper(168): 166, Š
b4: Ž isupper(180): 1 tolower(180): 184, ž
b8: ž islower(184): 1 toupper(184): 180, Ž
bc: Œ isupper(188): 1 tolower(188): 189, œ
bd: œ islower(189): 1 toupper(189): 188, Œ
be: Ÿ isupper(190): 1 tolower(190): 255, ÿ
c0: À isupper(192): 1 tolower(192): 224, à
c1: Á isupper(193): 1 tolower(193): 225, á
c2: Â isupper(194): 1 tolower(194): 226, â
c3: Ã isupper(195): 1 tolower(195): 227, ã
c4: Ä isupper(196): 1 tolower(196): 228, ä
c5: Å isupper(197): 1 tolower(197): 229, å
c6: Æ isupper(198): 1 tolower(198): 230, æ
c7: Ç isupper(199): 1 tolower(199): 231, ç
c8: È isupper(200): 1 tolower(200): 232, è
c9: É isupper(201): 1 tolower(201): 233, é
ca: Ê isupper(202): 1 tolower(202): 234, ê
cb: Ë isupper(203): 1 tolower(203): 235, ë
cc: Ì isupper(204): 1 tolower(204): 236, ì
cd: Í isupper(205): 1 tolower(205): 237, í
ce: Î isupper(206): 1 tolower(206): 238, î
cf: Ï isupper(207): 1 tolower(207): 239, ï
d0: Ð isupper(208): 1 tolower(208): 240, ð
d1: Ñ isupper(209): 1 tolower(209): 241, ñ
d2: Ò isupper(210): 1 tolower(210): 242, ò
d3: Ó isupper(211): 1 tolower(211): 243, ó
d4: Ô isupper(212): 1 tolower(212): 244, ô
d5: Õ isupper(213): 1 tolower(213): 245, õ
d6: Ö isupper(214): 1 tolower(214): 246, ö
d8: Ø isupper(216): 1 tolower(216): 248, ø
d9: Ù isupper(217): 1 tolower(217): 249, ù
da: Ú isupper(218): 1 tolower(218): 250, ú
db: Û isupper(219): 1 tolower(219): 251, û
dc: Ü isupper(220): 1 tolower(220): 252, ü
dd: Ý isupper(221): 1 tolower(221): 253, ý
de: Þ isupper(222): 1 tolower(222): 254, þ
e0: à islower(224): 1 toupper(224): 192, À
e1: á islower(225): 1 toupper(225): 193, Á
e2: â islower(226): 1 toupper(226): 194, Â
e3: ã islower(227): 1 toupper(227): 195, Ã
e4: ä islower(228): 1 toupper(228): 196, Ä
e5: å islower(229): 1 toupper(229): 197, Å
e6: æ islower(230): 1 toupper(230): 198, Æ
e7: ç islower(231): 1 toupper(231): 199, Ç
e8: è islower(232): 1 toupper(232): 200, È
e9: é islower(233): 1 toupper(233): 201, É
ea: ê islower(234): 1 toupper(234): 202, Ê
eb: ë islower(235): 1 toupper(235): 203, Ë
ec: ì islower(236): 1 toupper(236): 204, Ì
ed: í islower(237): 1 toupper(237): 205, Í
ee: î islower(238): 1 toupper(238): 206, Î
ef: ï islower(239): 1 toupper(239): 207, Ï
f0: ð islower(240): 1 toupper(240): 208, Ð
f1: ñ islower(241): 1 toupper(241): 209, Ñ
f2: ò islower(242): 1 toupper(242): 210, Ò
f3: ó islower(243): 1 toupper(243): 211, Ó
f4: ô islower(244): 1 toupper(244): 212, Ô
f5: õ islower(245): 1 toupper(245): 213, Õ
f6: ö islower(246): 1 toupper(246): 214, Ö
f8: ø islower(248): 1 toupper(248): 216, Ø
f9: ù islower(249): 1 toupper(249): 217, Ù
fa: ú islower(250): 1 toupper(250): 218, Ú
fb: û islower(251): 1 toupper(251): 219, Û
fc: ü islower(252): 1 toupper(252): 220, Ü
fd: ý islower(253): 1 toupper(253): 221, Ý
fe: þ islower(254): 1 toupper(254): 222, Þ
ff: ÿ islower(255): 1 toupper(255): 190, Ÿ
评论
wctomb
评论
toupper
对多字节编码一无所知,只知道单个字符。char
toupper
/tolower
只是不适用于除 ASCII(以及某些 ASCII 扩展名,具体取决于区域设置)之外的任何内容。如果您不想自己实现 Unicode 的(明显更复杂)正确的逻辑,您将不得不依赖像 ICU 这样的库。toupper
tolower
buf[0] == 0xef && buf[1] == 0xa3 && buf[2] == 0xbf
if (wctomb((char *)&buf, 0xf8ff) ==3 && buf[0] == 0xef && buf[1] == 0xa3 && buf[2] == 0xbf);
int main() { printf("%d\n", toupper(181)); }
toupper
isupper
isupper((unsigned char)toupper((unsigned char)181)))