为什么要将字符与0xff进行按位与操作?

8

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

        ..
}

3
对于isspace函数,如果*bp的值不能表示为unsigned char并且不等于EOF,其行为是未定义的。因此,可能需要使用一个复杂的强制类型转换,而不是使用(unsigned char) *bp。请问bpchar*类型吗? - Ted Lyngmo
2
在这种情况下,您实际上是在检查 bp 的最低字节,通过执行 & 0xff 来丢弃可能的其他字节,然后查看结果是否与空格字符匹配。 - h0r53
1
@RogerCostello 是的,但我认为问题与 bp 的类型关系较小,更多地与 isspace 的参数类型有关,它是一个 int,可能是多个字节。 - h0r53
1
@nielsen 这实际上是一种将其表示为“unsigned char”的方法,因为实现允许假设它会得到EOF或某些可表示为“unsigned char”的东西。我认为KamilCuk的答案已经解释清楚了。 - Ted Lyngmo
1
除了缺少包含文件和变量定义之外,缺失的部分是默认整数提升。[顺便说一句:@TedLyngmo:请不要在C问题上发布C++文档的参考。这两种语言是不同的。例如尝试sizeof('a')] 哦,还有char的符号 - wildplasser
显示剩余8条评论
6个回答

7

从C标准(7.4字符处理<ctype.h>)中:

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

在这个调用中:

isspace(* bp)

由于整数提升规则,类型为char的参数表达式 *bp 被转换为int类型。
如果char类型行为类似于signed char类型,并且表达式*bp的值为负,则int类型的晋升表达式的值也将是负数,无法表示为unsigned char类型的值。
这会导致未定义的行为。
在这个调用中:
isspace(* bp & 0xff)

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

因此,这是一种技巧,用来代替编写更清晰的代码,比如:

isspace( ( unsigned char )*bp )

isspace 函数通常用于将其 int 类型的参数作为索引在包含 256 个值(从 0 到 255)的表中进行查找实现。如果 int 类型的参数的值大于最大值 255 或者是一个负值(并且不等于宏 EOF 的值),则该函数的行为未定义。


3

来自cppreference isspace()如果ch的值不能表示为unsigned char并且不等于EOF,则其行为是未定义的

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

在二进制补码系统中,值被符号扩展到更大的“宽度”,然后左侧位将被设置。然后,当您使用更宽的类型的0xff时,左侧位将被清除,您最终得到一个正值,小于或等于0xff,即可表示为unsigned char

请注意,传递给 & 的参数会经过 隐式提升,因此在调用 isspace 之前,*bp 的结果就已经转换为 int。 假设例如 *bp = -42 并且假设平台是有理智的,具有带符号的 8 位字符和 32 位的 int,那么:
*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 实现 看起来就像
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]是无效的。

1
您的问题:

这个怎么样:

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

不同于这个:

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

区别在于,第一个示例中由于使用了完整掩码(0b111111110xff),你总是将bp的最低字节传递给isspace。可能isspace的参数包含比1个字节更大的类型。例如,isspace定义为isspace(int c),因此可以看到这里的参数是一个int,根据系统的不同,它可能有多个字节。
简而言之,这是一个合理性检查,以确保isspace只比较来自bp变量的单个字节。

1
但是 bp 是一个 char*,所以 *bp 是一个 char,即它占用一个字节。 - pmg
1
没错,但是 isspace 实际上接受的是一个整数而不是一个字节,因此需要进行一些强制类型转换。这基本上是一个健全性检查,以确保只使用了 bp 中的一个字节作为 isspace 的参数。 - h0r53
2
嗯...当 *bp 小于0时,它会被符号扩展为 int,因此它有更多的设置位,对吧? - KamilCuk
2
我明白了,你的意思是“将 *bp 转换为 int 后再取其中的一个字节”。 - pmg
1
是的,它似乎是“无意义的”,我相信在大多数情况下都是如此,但很可能存在一种边缘情况,这种显式比较是有用的(例如,隐式类型转换、符号扩展、Unicode等)。 - h0r53
显示剩余2条评论

1
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;

1

0

如果我们假设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 对象中,则生成的值是实现定义的,但必须在该类型可以表示的值范围内。
例如,德州仪器的 TMS320C28x DSP 具有 16 位的 char。
对于编译器指定 hereCHAR_BIT 为 16,见第 99 页。
这似乎是一款现代处理器(目前正在销售),支持 C99 和 C++03 的编译器。

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