为什么使用 0xff 对字符进行按位与运算?

问题描述

我正在阅读一些实现简单解析器的代码。名为 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;

        ..
}

解决方法

来自 C 标准(7.4 字符处理

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

在这次通话中

isspace(* bp)

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

如果类型 char 表现为类型 signed char 并且表达式 *bp 的值为负,则类型 int 的提升表达式的值也是将是负数,不能表示为 unsigned char 类型的值。

这会导致未定义的行为。

在这次通话中

isspace(* bp & 0xff)

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

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

isspace( ( unsigned char )*bp )

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

,

来自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 的结果在调用 int 之前被转换为 isspace。让我们假设 *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,...1,1,... };
// note: EOF is -1
#define isspace(x)  (bigarray[(x) + 1])

在这种情况下,您不能传递例如 -42,因为 bigarray[-41] 只是无效的。

,

您的问题:

这是怎么回事:

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

与此不同:

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

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

简而言之,这是一项完整性检查,以确保 isspace 仅比较 bp 变量中的一个字节。

,
s-1vcpu-1gb

&&

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

严格来说,如果 while (isspace(* bp)) ++ bp; 不引用 bp,两者都是不正确的。

在这种情况下应该是:

unsigned char

或更好

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

isspace 未定义如果参数不是 while (isspace(*bp == EOF ? EOF : (unsigned char)(*bp & 0xff))) ++ bp; 或它没有 EOF 的值

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

char
,

在 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 所见的值

,

如果我们假设 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 对象中,结果值是实现定义的,但 应在可以表示的值范围内 类型。

例如, 德州仪器 (TI) 的 TMS320C28x DSP 具有 16 位字符。
对于编译器指定 hereCHAR_BIT 为 16(第 99 页)。

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