【问题标题】:strcmp returning unexpected resultsstrcmp 返回意外结果
【发布时间】:2012-05-04 08:32:44
【问题描述】:

如果第一个字符串大于第二个字符串,我认为 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 好的,那么您将如何在您的实现中显示字符已签名?
  • 我不在乎。编辑:只要我在 2 的补码机器上。

标签: c char strcmp


【解决方案1】:

strcmp 和其他字符串函数实际上并不支持 utf。在大多数 posix 机器上,C/C++ char 内部是 utf8,这使得大多数事情在读写方面“正常工作”,并提供库理解和操作 utf 代码点的选项。但是默认的 string.h 函数对文化不敏感,并且不知道任何关于比较 utf 字符串的事情。您可以查看strcmp 的源代码并亲眼看看,它是一个尽可能幼稚的实现(这意味着它也比国际化感知比较函数更快)。

我刚刚回答了这个in another question - 您需要使用支持 UTF 的字符串库,例如 IBM 出色的 ICU - International Components for Unicode

【讨论】:

  • 我意识到 - 这就是为什么我说我还尝试保存在另一个字符集(在本例中为 Windows-1252),其中 'Ä' 是一个值为 -60 的字符。但这没有帮助,它仍然打印 -1。
【解决方案2】:

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

比较函数 memcmp、strcmp 和 strncmp 返回的非零值的符号由第一对字符的值之间的差异符号确定(均解释为无符号字符) 被比较的对象不同。

(强调我的)。

原因可能是这样的解释保持了公共编码中代码点之间的顺序,而将它们解释为带符号的chars 则没有。

【讨论】:

  • 更重要的是,如果一个字符串在某个位置有一个零字节,而另一个字符串有其他内容,则第一个字符串应该在第二个之前进行比较,即使是其他内容,解释为 @987654324 @, 是否定的。可以有一个特殊的规则将排名定义为 0,然后是 -128 到 -1,然后是 1 到 127,但这有点奇怪。
【解决方案3】:

strcmp() 将字符作为无符号 ASCII 值。所以,你的带双点的 A 不是 char -61,而是 char 195(或者可能是 196,如果我的数学错误的话)。

【讨论】:

  • 看起来就是这样,是的。但为什么呢?
  • @MrLister 在 iso-8859-1 或 Windows-1252 等 8 位编码中,代码点编号为 0-255。将字符串的内容视为unsigned char 会保留代码点的顺序,而将它们视为有符号则不会。类似地,对于像 utf-8 这样的编码,在将字节视为无符号时,更高的 unicode 代码点编号会产生更大的字节序列,但在将它们视为有符号时则不会。可能这就是strcmp 使用unsigned chars 的原因。
  • @DanielFischer 有道理。所以你的意思是它甚至不依赖于实现?哦,好吧,我想我可以忍受,但如果手册这么说,我真的很感激。
  • @MrLister 不,这是标准规定的,请参阅我的回答。我同意如果手册页这样说会很好。
【解决方案4】:

另存为 8 位 ASCII 编码,'A' == 65'Ä' 等于 -61,如果您认为它是 unsigned char。无论如何,'Ä' 是严格正数且大于 2^7-1,您只是将其打印为已签名。

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

【讨论】:

  • 您是说strcmp 将其参数视为无符号字符吗?我从未读过任何关于此的内容。
  • @MrLister:不。我是说char 是真的signed char 还是unsigned char 是由实现定义的。在您的情况下,它似乎是unsigned char,但您使用%i 来打印它的值。告诉printf(),您正在打印unsigned char 而不是signed int
  • @MrLister:这不是真的。你告诉 printf() 认为它的参数是signed int,而它的参数确实是一个unsigned char。使用正确的格式说明符,在本例中为 %c
  • 没有。这:printf("%u %u %u\n", (unsigned char)'Ä', (signed char)'Ä', (char)'Ä'); 将为 unsigned char 打印 196,但为另外两个打印 4294967236,证明 (char) 与 (signed char) 具有相同的标志。
  • 这很有趣,因为将值转换为大小相等的整数类型不应该影响输出。此外,4294967236 不能用 8 位表示。
【解决方案5】:

查看 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。这就是我问这个问题的原因。
  • 它显示为-1,因为字符串“A”小于“Ä”。您看到 -61 是因为您只打印了“Ä”字符串的第一个字节。
【解决方案6】:

当输入字符集超出时在 C 中正确处理字符串 UTF8 你应该使用标准库的宽字符工具 字符串和 i/o。你的程序应该是:

#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)。您不需要特殊的库。

【讨论】:

    猜你喜欢
    • 2017-11-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-10
    • 2016-02-24
    相关资源
    最近更新 更多