C语言strtod()在特殊Locale下匹配INFINITY的合规性问题
咱们来聊聊C语言里strtod()在特殊locale(比如土耳其语)下处理INFINITY大小写匹配的合规性问题——这是C标准(C23及回溯到C99)里一个比较小众的边角案例,核心点在于locale对大小写转换的特殊影响,以及strtod()的标准要求到底怎么界定这种情况。
问题背景:土耳其Locale的特殊大小写映射
Locale是C标准的一部分,不同locale的大小写转换规则可能和我们熟悉的ASCII规则大不一样。比如土耳其语locale里,有两个特殊字符的大小写映射会直接关联到ASCII的i和I:
- 带点的大写İ(编码
0xDD),调用tolower()会转换成ASCII的小写i(105) - 无点的小写ı(编码
0xFD),调用toupper()会转换成ASCII的大写I(73)
我们可以用一段简单代码验证这个特殊映射:
#include <ctype.h> #include <locale.h> #include <stdio.h> int main() { void *ptr = setlocale(LC_ALL, "turkish"); if (ptr) { #define TURKISH_I_WITH_DOT_ABOVE 0xDD printf("TURKISH_I_WITH_DOT_ABOVE %3d, tolower(TURKISH_I_WITH_DOT_ABOVE):%3d, i: %3d\n", TURKISH_I_WITH_DOT_ABOVE, tolower(TURKISH_I_WITH_DOT_ABOVE), 'i'); #define TURKISH_DOTLESS_I 0xFD printf("TURKISH_DOTLESS_I %3d, toupper(TURKISH_DOTLESS_I) :%3d, I: %3d\n", TURKISH_DOTLESS_I, toupper(TURKISH_DOTLESS_I), 'I'); } }
这段代码的运行输出如下:
TURKISH_I_WITH_DOT_ABOVE 221, tolower(TURKISH_I_WITH_DOT_ABOVE):105, i: 105 TURKISH_DOTLESS_I 253, toupper(TURKISH_DOTLESS_I) : 73, I: 73
strtod()的实际表现
既然这些特殊字符的大小写转换会关联到ASCII的i/I,那strtod()在土耳其locale下,能不能识别以这些特殊字符开头的"nfinity"(比如0xDD+"nfinity")作为INFINITY呢?我们写代码实际测试一下:
#include <locale.h> #include <stdio.h> #include <stdlib.h> int main() { void *ptr = setlocale(LC_ALL, "turkish"); if (ptr) { char *end; char infinity[] = "infinity"; double y = strtod(infinity, &end); printf("strtod(%s, &end) --> %g %td\n", infinity, y, end - infinity); #define TURKISH_I_WITH_DOT_ABOVE 0xDD infinity[0] = (char) TURKISH_I_WITH_DOT_ABOVE; y = strtod(infinity, &end); printf("strtod(%s, &end) --> %g %td\n", infinity, y, end - infinity); #define TURKISH_DOTLESS_I 0xFD infinity[0] = (char) TURKISH_DOTLESS_I; y = strtod(infinity, &end); printf("strtod(%s, &end) --> %g %td\n", infinity, y, end - infinity); } }
实际运行输出是:
strtod(infinity, &end) --> inf 8 strtod(�nfinity, &end) --> 0 0 strtod(�nfinity, &end) --> 0 0
可以看到,strtod()只识别了标准ASCII的i开头的"infinity",对土耳其语的特殊字符开头的版本完全没识别,直接返回0,匹配长度为0。
从C标准条款分析合规性
要判断这种实现是否合规,我们得从C23的标准条款里找依据:
- 字符集定义(§5.2.1 3):
字母指前面定义的大写拉丁字母或小写拉丁字母;本文档中,该术语不包含其他字母表中的字符。
也就是说,C标准里定义的“字母”仅指26个拉丁字母的大小写,不包含土耳其语的这些特殊字符。
- islower函数的定义(§7.4.2.7):
islower函数测试任何小写字母,或者locale特定的、不属于控制字符、数字、标点或空白的字符。在"C"locale下,islower仅对小写字母返回真。
这里说明locale可以扩展“小写字母”的范围,但C标准的核心字母集还是拉丁字母。
- strtod()的要求(§7.24.1.5 3):
主题序列的预期形式是可选的正负号,然后是下列之一:…… — INF或INFINITY,忽略大小写。
- 非C locale的扩展(§7.24.1.5 7):
在非"C"locale下,可能接受额外的locale特定的主题序列格式。
核心结论
从这些条款可以推导:
- 首先,
strtod()必须识别标准拉丁字母大小写组合的INF/INFINITY(比如inf、Inf、INFINITY等),这是强制要求。 - 对于locale特定的字符(比如土耳其语的带点İ/无点ı),标准并没有强制要求
strtod()必须通过locale的tolower/toupper来识别它们作为INFINITY的一部分——因为标准里的“忽略大小写”是针对它定义的拉丁字母集的。 - 反过来,如果某个C库的
strtod()实现选择适配locale的大小写转换,识别这些特殊字符开头的字符串,这也属于标准允许的“locale特定的额外格式”,同样是合规的。
所以你测试的这个C库实现(不识别土耳其语特殊字符开头的)是完全合规的;如果有其他实现识别了,也不违反标准——标准给了实现者在locale扩展上的选择权。
内容来源于stack exchange

