用十六进制值比较字符

13

在过去的一天里,我的代码出现了一个严重的bug。经过一些搜索,发现这个问题似乎与char值和16进制的比较有关。我的编译器是gcc 4.4.1,在Windows上运行。 我在下面的简单代码中重现了这个问题:

char c1 = 0xFF; char c2 = 0xFE;
if(c1 == 0xFF && c2 == 0xFE)
{
    //do something
}
出乎意料的是,上面的代码并没有进入循环。我完全不知道为什么,真的很需要一些帮助。这太荒谬了,解决方案肯定是(像总是一样)我犯了一个我完全忽视的巨大错误。
如果我用unsigned chars替换上面的代码,它就有效了,但只在某些情况下有效。我正在努力找出原因。此外,如果我将十六进制值强制转换为char进行比较,则会正确地进入循环,如下所示:
if(c1 == (char)0xFF && c2 == (char)0xFE)
{
    //do something
}

这是什么意思?为什么会发生这种情况?默认情况下,原始十六进制值不会被解释为字符吗? 对于好奇的人,我在代码中第一次注意到它是将流的前两个字节与上述十六进制值及其反向进行比较来识别字节顺序标记。

非常感谢任何帮助。

5个回答

13

普通的 char 可以是 signed 或者 unsigned。如果类型是 unsigned,那么一切都如你所料。

如果类型是 signed,那么将 0xFF 赋给 c1 将会导致在比较执行时值被提升为 -1,但 0xFF 是一个正常的正整数,所以 -1 == 0xFF 的比较失败。

请注意类型 charsigned charunsigned char 是不同的,但其中两个具有相同的表示方式(其中一个是 char)。


你有几种选择。你可以将十六进制值转换为(char)类型,编译器会像它在比较操作符另一侧的char变量时一样进行转换。或者你可以将char类型的变量强制转换为(unsigned char)类型。注意,转换为(unsigned)类型并不明智;在一个 int 为4字节的机器上,将 -1 转换为 unsigned 会生成0xFFFFFFFF而不是0xFF。另一种选择是使用一个数组 char charmap [256],初始化 charmap [i] = i;,然后比较 c1 == charmap [0xFE]。或者你也可以直接使用 unsigned char - Jonathan Leffler
@JonathanLeffler,你确定在4字节机器上将-1(有符号的0xFF)转换为无符号时会扩展为0xFFFFFFFF吗?试试 signed char x = 0xFF; printf("%08x\n", x); printf("%08x\n", (unsigned char)x);。我认为@paxdiablo在他的评论中解释得正确。 - abdus_salam
@abdus_salam:是的,我确定。此外,当我尝试您的代码片段时,我得到两行输出:“ffffffff”和“000000ff”,这正是我所期望的。(您期望什么?您在尝试时看到了什么?)请注意,printf()的参数被转换为int(因为在调用像printf()这样的可变参数函数时,所有char的变体都会被提升为int),然后在printf()内部,格式指示参数应被视为unsigned int - Jonathan Leffler
@JonathanLeffler 如果我的星期五下午的方眼睛漏看了什么,请原谅,但是是的,那就是我看到和期望的,并且似乎与您最初的评论不符。第二个printf语句将x转换为“unsigned int”,我们得到的是000000FF而不是您所建议的所有FF。我的观点是,只要您不关心原始值的符号,将有符号字符转换为无符号字符是安全的。您是否不同意? - abdus_salam
@abdus_salam:我认为我们对这些单词的含义存在误解。我承认我不确定问题出在哪里,也不知道应该注释哪些语句。也许你可以给我发送一封电子邮件,并注明你遇到了哪些我写的语句有问题(复制粘贴),当我更加明确你认为我说错了什么时,我可以解释任何需要解释的内容。在你的第一条评论中,你提到了“转换为无符号数”;如果你在回复我的第一条评论(针对Lefteris),那么我所指的是(unsigned)-1,并没有涉及到char类型。 - Jonathan Leffler
显示剩余2条评论

8

当比较字符和十六进制时,必须小心:

使用“==”运算符将字符与0x80进行比较总是会得到false的结果吗?

我建议使用C99中引入的这种语法来确保。

if(c1 == '\xFF' && c2 == '\xFE')
{
    // do something
}

避免使用强制类型转换,这是不必要的且不安全。它告诉编译器0xFF是char而不是int,这将解决你的问题。clang编译器也会警告你:常量128与类型为'char'的表达式的比较始终为false [-Werror,-Wtautological-constant-out-of-range-compare]。

4
字面值0xff不是char类型,它是int类型(有符号)。当你将其强制转换为char变量时,它会适配但取决于你的char类型是有符号还是无符号,这会影响到它在表达式中的升级(见下文)。
在类似if(c1==0xff)的表达式中,c1变量将被提升为整数,因为0xff就是整数。而它被提升后依赖于它是否被标记为有符号。
最后,你可以采取以下两种方法之一:
1. 确保使用signed char类型,以便它“符号扩展”到正确的int类型。我的意思是无符号char 0xff将变为(对于4字节的int)0x000000ff(因此仍然是255),而有符号的char将变为0xffffffff(因此仍然是-1)。
2. 将字面值强制转换为相同类型的变量,这已经通过(char)oxff实现了。

谢谢paxdiablo,好答案。我想知道在您看来最安全的方法是什么,保证在任何地方都能正常工作?现在我需要在代码中更改各个位置,以反映这个错误的认识,我只是不想在将来回到它并注意到它在某些特殊情况下会破坏我的所有代码。 - Lefteris
1
@Lefteris:就代码而言,我们所看到的两种方法都可以正常工作。这并不是说你在其他方面没有一些假设会受到你的选择的影响。就我个人而言,我更喜欢我的“char”变量是无符号的,然后使用常量如255U(以确保它们匹配有符号性)。但这是各有千秋。 - paxdiablo
谢谢。不幸的是,我在项目早期没有发现这个错误,所以我需要做很多修复工作。 - Lefteris
@Lefteris:是的,你需要这样做。祝你好运 :-) - paxdiablo

0

我通过将变量转换为UINT16(针对我的编译器进行了优化)来解决了这个问题。在你的情况下,你需要将c1和c2转换为INT。

char c1 = 0xFF; char c2 = 0xFE;
if((int)c1 == 0xFF && (int)c2 == 0xFE)
{
    //do something
}

0

字符0xFE将被翻译为负整数。表达式中的常量将被翻译为正整数。


0xFE是一个正数。如果它被赋值给一个char类型的变量,并且char是有符号类型,那么当该值在比较时被提升为int类型时,就会...但这一切都取决于char是否为有符号类型,这并不是自动的情况(尽管对于OP来说确实如此)。 - Jonathan Leffler

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