如何解决为什么使用 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 头文件
在这次通话中
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
,这是由于具有完整位掩码(0b11111111
或0xff
)。 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
请参阅 http://cpp.sh/9mp2i 以了解它如何按位更改并更改 isspace 所见的值
,如果我们假设 char 类型的位总是 8,
那么这里带有 0xff 的代码按位与运算符会让我们感到困惑。
但是如果 char 类型不总是 8 位呢?
那么0xff可能还有别的意思吧?
实际上,char 类型并不总是 8 位,我们可以在 C99 标准中看到详细信息。标准中的char类型没有定义为8位。
以下是C99标准对char类型大小的描述。
6.5.3.4 sizeof 运算符当应用于具有 char、unsigned char 或 signed char 类型的操作数时,(或合格版本) 结果是 1。当应用于具有数组类型的操作数时, 结果是数组中的总字节数。)当应用于 具有结构或联合类型的操作数,结果是总数 此类对象中的字节数,包括内部和尾随 填充。
6.2.5 类型 声明为 char 类型的对象足够大,可以存储基本执行字符集的任何成员。如果是会员 基本执行字符集存储在一个 char 对象中,它的 值保证为正。如果存储了任何其他字符 在 char 对象中,结果值是实现定义的,但 应在可以表示的值范围内 类型。
例如,
德州仪器 (TI) 的 TMS320C28x DSP 具有 16 位字符。
对于编译器指定 here,CHAR_BIT 为 16(第 99 页)。
这似乎是一个现代处理器(目前正在销售),编译器支持 C99 和 C++03。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。