strcmp 返回意外结果

3

我曾认为strcmp函数应该在第一个字符串大于第二个字符串时返回正数。但是这个程序

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

int main()
{
    char A[] = "A";
    char Aumlaut[] = "Ä";
    printf("%i\n", A[0]);
    printf("%i\n", Aumlaut[0]);
    printf("%i\n", strcmp(A, Aumlaut));
    return 0;
}

输出结果为65-61-1

为什么会这样呢?我有什么遗漏的地方吗?
我以为可能是因为保存为UTF-8格式导致的。你知道,因为Ä在那里由2个字符组成。但是,使用8位编码并确保两个字符串都具有长度为1并没有帮助,最终结果是相同的。
我做错了什么?

在此使用的是32位Linux下的GCC 4.3,如果有影响的话。


您正在使用错误的格式说明符。请尝试使用%c以获得正确的结果。 - Philip
@Philip 好的,那么你如何在你的实现中显示字符是有符号的呢? - Mr Lister
我不在乎。编辑:只要我在一个二进制补码机器上。 - Philip
6个回答

2
“strcmp”和其他字符串函数实际上并不支持utf。在大多数posix机器上,C/C++中的“char”内部是utf8格式,这使得大多数读写操作“只需工作”,并提供了库理解和操作utf码点的选项。但默认的“string.h”函数不具有文化敏感性,并且不知道如何比较utf字符串。您可以查看“strcmp”的源代码,自己看看,它是尽可能天真的实现(这意味着它比国际化感知的比较函数更快)。
我刚刚在另一个问题中回答了这个问题 - 您需要使用支持UTF的字符串库,例如IBM出色的ICU-Unicode国际组件

我意识到了 - 这就是为什么我说我也尝试过在另一个字符集(在这种情况下是Windows-1252)中保存,其中 'Ä' 是一个值为-60的字符。但是那并没有帮助,它仍然打印-1。 - Mr Lister

1

以8位ASCII编码保存,'A' == 65,而'Ä'等于-61(如果你将其视为unsigned char)。无论如何,'Ä'是严格正数且大于2^7-1,你只是将它打印出来,好像它是有符号的。

如果你将'Ä'视为unsigned char(它确实是),那么在你的字符集中它的值为195。因此,strcmp(65, 195)正确报告了-1


你是说 strcmp 把它的参数当作 无符号字符 处理吗?我从来没有读到过这方面的内容。 - Mr Lister
1
@MrLister:不是,我是说char到底是一个signed char还是一个unsigned char是由实现定义的。在你的情况下,它似乎是unsigned char,但你正在使用%i打印它的值。告诉printf()你正在打印一个unsigned char而不是一个signed int - Philip
如果它足够聪明,可以在有符号字符上执行无符号比较,那么这不应该在文档中记录吗?我不喜欢这样的意外。 - Mr Lister
真的。另一方面,我不知道任何具有负值的字符集。 - Philip
@Philip:符号扩展只会发生在负值上。实际发生的是整数提升。当晋升类型的值可以用目标类型表示时,晋升类型的过程保持值不变。在Windows-1252中,“Ä”作为无符号字符的值为196,作为有符号字符的值为-60。这两个值都可以被表示为“int”,因此调用是printf("%u %u %u\n",196,-60,-60);。尽管严格来说,这是未定义的行为——“%u”需要一个“unsigned int”,但您几乎可以依赖于“int”的位模式被解释为“unsigned int”。 - Daniel Fischer
显示剩余9条评论

1

strcmp()函数使用无符号ASCII值作为字符。因此,你的带双点的A不是字符-61,而是字符195(如果我算错了,可能是196)。


这似乎是它的作用,是吗?但为什么呢? - Mr Lister
1
@MrLister 在像iso-8859-1或Windows-1252这样的8位编码中,代码点编号为0-255。将字符串内容视为“unsigned char”可以保留代码点的顺序,将其视为有符号则不行。同样,在像utf-8这样的编码中,较高的Unicode代码点编号在将字节视为无符号时产生一个词典上更大的字节序列,但在将它们视为有符号时则不会。可能这就是为什么strcmp使用unsigned char的原因。 - Daniel Fischer
@DanielFischer 有道理。所以你的意思是这甚至不依赖于实现?噢好吧,我想我可以接受这一点,但如果手册能够说明这一点,我会非常感激的。 - Mr Lister
@MrLister 不是,这是标准规定的,请看我的回答。我同意如果手册上写明会更好。 - Daniel Fischer

1

strcmp和类似的比较函数将字符串中的字节视为unsigned char,如标准第7.24.4节第1点所指定(在C99中为7.21.4)。

比较函数memcmpstrcmpstrncmp返回非零值的符号由被比较对象中第一对不同字符的值的差异的符号决定(两个字符都解释为unsigned char)

(强调是我的)。

原因可能是这样的解释可以维护常见编码中代码点之间的排序,而将它们解释为有符号的char则不能。


更重要的是,如果一个字符串在某个位置有一个零字节,而另一个字符串有其他内容,那么第一个字符串应该在第二个字符串之前进行比较,即使将那些其他内容解释为char时会产生负数。可以制定特殊规则来定义排名为0,然后是-128到-1,然后是1到127,但这可能有点奇怪。 - supercat

0

请查看strcmp手册:

The strcmp() function compares the two strings s1 and s2. It returns
an integer less than, equal to, or greater than zero if s1 is found,
respectively, to be less than, to match, or be greater than s2.

但它并没有说-60大于65。这就是为什么我问这个问题的原因。 - Mr Lister
它显示为-1是因为字符串"A"小于"Ä"。你看到-61,因为你只打印了"Ä"字符串的第一个字节。 - ott--

-1
要正确处理C语言中输入字符集超过UTF8的字符串操作,你应该使用标准库提供的宽字符集合和I/O功能。你的程序应该是:
```c #include #include int main() { wchar_t str[] = L"你好,世界!"; wprintf(L"%ls", str); return 0; } ```
#include <wchar.h>
#include <stdio.h>

int main()
{
    wchar_t A[] = L"A";
    wchar_t Aumlaut[] = L"Ä";
    wprintf(L"%i\n", A[0]);
    wprintf(L"%i\n", Aumlaut[0]);
    wprintf(L"%i\n", wcscmp(A, Aumlaut));
    return 0;
}

然后它将会给出正确的结果(GCC 4.6.3)。你不需要特殊的库。


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