strcasecmp算法存在缺陷吗?

34

我正在尝试在C中重新实现strcasecmp函数,并注意到比较过程中似乎存在不一致。

man strcmp中可以得知:

strcmp()函数比较两个字符串s1和s2。 它不考虑区域设置(对于区域设置感知比较,请参见strcoll(3))。 如果找到s1小于,等于或大于s2,则返回一个整数小于,等于或大于零。

man strcasecmp中可以得知:

strcasecmp()函数执行逐字节比较字符串s1和s2,忽略字符的大小写。如果找到s1小于,等于或大于s2,则返回一个整数小于,等于或大于零。

int strcmp(const char *s1, const char *s2);
int strcasecmp(const char *s1, const char *s2);

根据这些信息,我不理解以下代码的结果:

#include <stdio.h>
#include <string.h>

int main()
{
    // ASCII values
    // 'A' = 65
    // '_' = 95
    // 'a' = 97

    printf("%i\n", strcmp("A", "_"));
    printf("%i\n", strcmp("a", "_"));
    printf("%i\n", strcasecmp("A", "_"));
    printf("%i\n", strcasecmp("a", "_"));
    return 0;
}

输出:
-1  # "A" is less than "_"
1   # "a" is more than "_"
2   # "A" is more than "_" with strcasecmp ???
2   # "a" is more than "_" with strcasecmp

看起来,如果s1中的当前字符是一个字母,它总是被转换为小写,无论s2中的当前字符是否是字母。

有人能解释一下这种行为吗?难道第一行和第三行不应该是相同的吗?

提前感谢!

PS:
我在Manjaro上使用gcc 9.2.0
此外,当我使用-fno-builtin标志进行编译时,我得到的结果是:

-30
2
2
2

我猜是因为该程序没有使用gcc的优化函数,但问题仍然存在。


2
将另一个测试用例添加到您的集合中:printf("%i\n", strcasecmp("a", "_")); 这应该与 printf("%i\n", strcasecmp("A", "_")); 有相同的结果。但这意味着这两个不区分大小写的调用中的 一个 将与其区分大小写的对应项产生不一致。 - anton.burger
看起来你所参考的 strcasecmp 描述并不准确。更多细节请查看得到赞同的回答。 - Jabberwocky
9
这是唯一有意义的。一个函数说“A < _ && a > _ && A == a”会引起很多问题。 - ikegami
@chux-ReinstateMonica 感谢您的提示。实际上,我想在汇编中实现它,但我没有提到它以避免混淆,因为这并不是必要的。 - Haltarys
1
关于内置函数和非内置函数输出结果的差异:两者都是相同的,因为它们的结果都是完全相同的<0和>0,并且你没有一个==0的例子。但是你可以看到算法的闪光点:一些返回值是第一个不相等字符的差异。 - the busybee
显示剩余2条评论
4个回答

29
行为是正确的。
根据POSIX str\[n\]casecmp()规范
当使用的区域设置的LC_CTYPE类别来自POSIX区域设置时,这些函数的行为就像字符串已转换为小写字母,然后执行字节比较一样。否则,结果是未指定的。
这也是Linux man页面的NOTES部分之一
POSIX.1-2008标准对这些函数说:
当使用的区域设置的LC_CTYPE类别来自POSIX区域设置时,这些函数的行为就像字符串已转换为小写字母,然后执行字节比较一样。否则,结果是未指定的。
为什么? 如@HansOlsson在他的答案中指出的那样,在只有字母之间进行不区分大小写的比较并允许所有其他比较具有其“自然”结果的strcmp()会破坏排序。
如果'A' == 'a'(不区分大小写比较的定义),那么'_' > 'A''_' < 'a'(ASCII字符集中的“自然”结果)不能同时为真。

在仅比较字母时进行不区分大小写的比较,结果不会是 '_' > 'A' && '_' < 'a';这似乎不是最好的例子。 - Asteroids With Wings
1
@AsteroidsWithWings 这些是问题中使用的字符。如果'a' == 'A' 根据定义,如果您在'a''A''_'的“自然”值之间进行比较,则不能进行不区分大小写的比较以获得相等性并获得一致的排序结果。 - Andrew Henle
我不否认这一点,但是你提供的具体反例似乎与此无关。 - Asteroids With Wings
好的,那我建议在回答中演示一下,因为目前它只是跳到指出 "'_' > 'A''_' < 'a' 不能同时成立",而没有告诉我们为什么我们曾经认为它会成立。 (这是回答者的任务,而不是数百万读者之一的任务。) - Asteroids With Wings
@AsteroidsWithWings 我知道这句话的意思... 那就不要有选择性地误引用一些没有问题的东西,以一种暗示明确陈述被省略了的方式。没有人会阻止你发布你认为更好的答案。 - Andrew Henle
显示剩余4条评论

22

其他链接,http://man7.org/linux/man-pages/man3/strcasecmp.3p.html。对于strcasecmp所述的转换为小写是正确的行为(至少在POSIX字符集环境下)。

这种行为的原因是,如果您使用strcasecmp对字符串数组进行排序,则需要得到合理的结果。

否则,如果您尝试使用例如qsort对"A"、"C"、"_"、"b"进行排序,则结果将取决于比较的顺序。


3
若您尝试使用qsort对"A"、"C"、"_"和"b"进行排序,结果会受到比较的顺序影响。这是一个很好的观点。这很可能是POSIX定义行为的原因。 - Andrew Henle
6
更具体地说,你需要一个全序来进行排序,如果你按照问题中的方式定义比较(因为它不是传递的),那么这将不成立。 - Bernhard Barker

8
看起来,如果s1中的当前字符是字母,则始终将其转换为小写字母,而不管s2中的当前字符是否为字母。
这是正确的 - 这就是strcasecmp()函数应该做的!它是一个POSIX函数,而不是C标准的一部分,但从“The Open Group Base Specifications, Issue 6”中可以看出:
在POSIX语言环境中,strcasecmp()和strncasecmp()的行为就像字符串已被转换为小写字母,然后执行字节比较一样。在其他语言环境中,结果是未指定的。
顺便说一下,这种行为也与_stricmp()函数(在Visual Studio/MSCV中使用)有关:
_stricmp函数将字符串1和字符串2转换为小写字母后进行顺序比较,并返回一个表示它们关系的值。

2
A 的 ASCII 十进制码是 65_95a97,所以 strcmp() 做的正是它应该做的。从字典上来说,_a 小且比 A 大。 strcasecmp() 将把 A 视为 a*,由于 a_ 大,输出结果也是正确的。
* POSIX.1-2008 标准对这些函数 (strcasecmp() 和 strncasecmp()) 有如下规定:

当使用的区域设置的 LC_CTYPE 类别来自 POSIX 区域设置时,这些函数将表现得好像字符串已转换为小写字母,然后执行字节比较。否则,结果是未指定的。

来源:http://man7.org/linux/man-pages/man3/strcasecmp.3.html

3
OP的观点是,在不区分大小写的情况下,A_“更大”,并想知道为什么与区分大小写时的结果不同。 - anton.burger
6
由于strcasecmp()不区分大小写,它将把A视为一种的说法是无效的推断。一个不区分大小写的例程可以将所有大写字母视为小写字母,可以将所有小写字母视为大写字母,或者可以将每个大写字母视为等于其相应的小写字母,反之亦然,但仍将它们与非字母字符以它们的原始值进行比较。这个答案没有说明为什么偏好其中任何一种可能性(正确的原因在于文档建议使用小写)。 - Eric Postpischil
@EricPostpischil POSIX.1-2008标准对这些函数(strcasecmp()和strncasecmp())的规定是:当使用的区域设置的LC_CTYPE类别来自POSIX区域设置时,这些函数应该表现得好像字符串已经被转换为小写字母,然后执行一个字节比较。否则,结果是未指定的。 - anastaciu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接