【问题标题】:Why do a bitwise-and of a character with 0xff?为什么用 0xff 对字符进行按位与?
【发布时间】:2021-08-13 02:06:02
【问题描述】:

我正在阅读一些实现简单解析器的代码。一个名为scan 的函数将一行分成标记。 scan 有一个静态变量 bp,它分配了要标记的行。在赋值之后,空白被跳过。见下文。我不明白的是为什么代码会按位和bp 指向0xff 的字符,即* bp & 0xff 的目的是什么?这是怎么回事:

while (isspace(* bp & 0xff))
    ++ bp;

与此不同:

while (isspace(* bp))
    ++ bp;

这里是scan 函数:

static enum tokens scan (const char * buf)
                    /* return token = next input symbol */
{   static const char * bp;

    while (isspace(* bp & 0xff))
        ++ bp;

        ..
}

【问题讨论】:

  • 对于isspace,如果*bp 的值不能表示为unsigned char 并且不等于EOF,则行为未定义。- 所以也许这是一个花哨的演员阵容,而不是做(unsigned char) *bpbpchar* 吗?
  • 查找位掩码的概念。基本上,0xff 转换为二进制的11111111,或所有1 的单个字节。例如,如果您只需要一个字节的数据而不是整个值(可能是多个字节),这将很有用。例如,int 可能是 4 个字节,所以如果您只想要最低的 1 个字节,您可以简单地使用int_variable & 0xff 来获取值。
  • 在这种情况下,您实际上是在检查bp 的最低字节,丢弃可能的其他字节(通过执行& 0xff)然后查看结果是否与空白字符匹配。
  • 太棒了!谢谢你。不过,我还是有点疑惑。 "bp" 是这样声明的: static const char * bp;这不是意味着 bp 总是指向一个(1 字节)字符吗?在这种情况下,在检查空格时没有其他字节可以丢弃或关心,对吧?
  • @RogerCostello 是的,但我认为这个问题与bp 的类型关系不大,更多的是与isspace 的参数类型有关,即int,可能是多个字节。

标签: c char bitwise-and integer-promotion isspace


【解决方案1】:

如果我们假设 char 类型的位数总是 8,
那么这里带 0xff 的代码按位与运算符会让我们感到困惑。

但是如果 char 类型并不总是 8 位呢?
那么0xff可能还有别的意思吧?

其实char类型并不总是8位,我们可以在C99标准中看到细节。标准中的char类型没有定义为8位。

以下是C99标准对char类型大小的描述。

6.5.3.4 sizeof 运算符当应用于类型为 charunsigned charsigned char 的操作数时,(或合格的版本) 结果为 1。当应用于具有数组类型的操作数时, 结果是数组中的总字节数。)当应用于 具有结构或联合类型的操作数,结果是总数 此类对象中的字节数,包括内部和尾随 填充。

6.2.5 类型 声明为 char 类型的对象大到足以存储基本执行字符集的任何成员。如果是会员 基本执行字符集的存储在一个 char 对象中,它的 值保证为正。如果存储了任何其他字符 在 char 对象中,结果值是实现定义的,但 应在可以表示的值范围内 输入。

例如, Texas Instruments 的 TMS320C28x DSP 具有 16 位字符。
对于编译器指定 hereCHAR_BIT as 16(第 99 页)。

这似乎是一个现代处理器(目前正在出售),编译器支持 C99 和 C++03。

【讨论】:

    【解决方案2】:

    来自 C 标准(7.4 字符处理

    1 头文件 声明了几个有用的函数 分类和映射字符。198) 在所有情况下,论点都是 一个 int,其值应表示为无符号数 char 或应等于宏 EOF 的值。如果论证有 任何其他值,行为未定义。

    在本次通话中

    isspace(* bp)
    

    由于整数提升,char 类型的参数表达式 *bp 被转换为 int 类型。

    如果char 类型的行为与signed char 类型一样,并且表达式*bp 的值为负,那么int 类型的提升表达式的值也将是负的并且不能表示作为 unsigned char 类型的值。

    这会导致未定义的行为。

    在本次通话中

    isspace(* bp & 0xff)
    

    由于位运算符和int 类型的表达式* bp & 0xff 的结果值可以表示为unsigned char 类型的值。

    所以这是一个技巧,而不是像编写更清晰的代码

    isspace( ( unsigned char )*bp )
    

    函数isspace 通常是这样实现的,它使用类型为int 的参数作为具有256 个值(从0 到255)的表中的索引。如果int 类型的参数的值大于最大值 255 或负值(并且不等于宏 EOF 的值),则函数的行为未定义。

    【讨论】:

      【解决方案3】:

      in c char 可以有符号也可以无符号https://en.wikipedia.org/wiki/C_data_types

      当传递给isspace 时,bp 将被提升为整数。如果它是有符号的并且设置了高位,那么它将被符号扩展为一个负整数。这可能意味着它不是 isspace 函数 https://linux.die.net/man/3/isspaceNo 所要求的无符号字符或 EOF

      请参阅http://cpp.sh/9mp2i,了解它如何更改按位并更改 isspace 所看到的值

      【讨论】:

        【解决方案4】:

        来自cppreference isspace()The behavior is undefined if the value of ch is not representable as unsigned char and is not equal to EOF

        *bp 为负数时,例如-42,则不能表示为unsigned char,因为它是负数,而unsigned char,嗯,必须是正数或零。

        二进制补码系统的值是sign extended 到更大的“宽度”,因此它们将设置最左边的位。然后当你取更宽类型的0xff时,最左边的位被清除,你最终得到一个正值,小于或等于0xff,我的意思是可以表示为unsigned char

        注意& 的参数经过implicit promotions,所以*bp 的结果在调用isspace 之前会转换为int。让我们假设 *bp = -42 为例,并假设一个健全的平台,带有 8 位已签名字符,int 有 32 位,然后:

        *bp & 0xff               # expand *bp = -42
        (char)-42 & 0xff         # apply promotion
        (int)-42 & 0xff          # lets convert to hex assuming twos-complement
        (int)0xffffffd6 & 0xff   # do & operation
        (int)0xd6                # lets convert to decimal
        214                      # representable as unsigned char, all fine
        

        如果没有& 0xff,负值将导致未定义的行为。

        我会建议首选isspace((unsigned char)*bp)

        基本上最简单的isspace实现looks like just

        static const char bigarray[257] = { 0,0,0,0,0,...1,0,1,0,... };
        // note: EOF is -1
        #define isspace(x)  (bigarray[(x) + 1])
        

        在这种情况下你不能通过例如-42,因为bigarray[-41]是无效的。

        【讨论】:

          【解决方案5】:
          while (isspace(* bp & 0xff))
              ++ bp;
          

          &&

          while (isspace(* bp))
              ++ bp;
          

          严格来说,如果bp 没有引用unsigned char,两者都是不正确的。

          在这种情况下应该是:

          while (isspace((unsigned char)(*bp & 0xff)))
              ++ bp;
          

          或更好

          while (isspace(*bp == EOF ? EOF : (unsigned char)(*bp & 0xff)))
              ++ bp; 
          

          如果参数不是EOF 或者它没有unsigned char 的值,则isspace 未定义

          如果 *bp 引用 char 它必须是:

          while (isspace((unsigned char)(*bp)))
              ++bp;
          

          【讨论】:

            【解决方案6】:

            你的问题:

            这是怎么回事:

            while (isspace(* bp & 0xff))
                ++ bp;
            

            与此不同:

            while (isspace(* bp))
                ++ bp;
            

            不同之处在于,在第一个示例中,您始终将bp 处的最低字节传递给isspace,这是由于与完整位掩码(0b111111110xff)的按位与结果。 isspace 的参数可能包含大于 1 个字节的类型。例如,isspace 定义为isspace(int c),因此您可以看到这里的参数是int,根据您的系统,它可能是多个字节。

            简而言之,这是一项健全性检查,可确保 isspace 仅比较您的 bp 变量中的单个字节。

            【讨论】:

            • ...但是bpchar*,所以*bpchar,即它占用一个字节。
            • 没错,但isspace 实际上需要一个int,而不是一个字节,所以会发生一些转换。这基本上是一个健全性检查,以确保只有来自 bp 的单个字节用于 isspace 的参数
            • hm...当*bp小于0时,它被符号扩展为int,所以它有更多的设置位,对吧?
            • 我明白了,你的意思是“*bp 中的一个字节(自动)转换为int
            • 是的,它看起来确实“毫无意义”,我敢肯定在大多数情况下确实如此,但可能存在这种类型的显式比较有用的边缘情况(例如,隐式类型转换,符号扩展、unicode 等)。
            猜你喜欢
            • 1970-01-01
            • 2015-07-06
            • 2013-10-04
            • 1970-01-01
            • 1970-01-01
            • 2017-03-21
            • 2018-07-06
            • 2018-09-15
            • 1970-01-01
            相关资源
            最近更新 更多