我正在阅读一些实现简单解析器的代码。名为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,其值应表示为无符号 字符或应等于宏 EOF 的值。如果参数有 任何其他值,行为都是未定义的。
在此通话中
isspace(* bp)
由于整数提升,具有类型char
的参数表达式*bp
转换为类型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
,嗯,必须是正数或零。
在二进制补码系统上,值被符号扩展到更大的"宽度",因此它们将设置最左边的位。然后,当您采用较宽类型的0xff
时,清除最左侧的位,最终得到一个正值,低于或等于0xff
,我的意思是可表示为unsigned char
。
请注意,&
参数会进行隐式提升,因此*bp
的结果甚至在调用isspace
之前就转换为int
。让我们假设*bp = -42
例如,假设一个理智的平台具有 8 位字符,该 char 是有符号的,并且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
实现看起来像:
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]
只是无效。
你的问题:
这是怎么回事:
while (isspace(* bp & 0xff))
++ bp;
与此不同的是:
while (isspace(* bp))
++ bp;
不同之处在于,在第一个例子中,你总是将最下面的字节传递给bp
到isspace
,这是由于具有完整位掩码(0b11111111
或0xff
)的按位AND的结果。要isspace
的参数可能包含大于 1 个字节的类型。例如,isspace
被定义为isspace(int c)
,所以你可以看到这里的参数是一个int
,根据你的系统,它可能是多个字节。
简而言之,这是一种健全性检查,以确保isspace
只比较bp
变量中的一个字节。
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;
在 c 字符中可以是有符号的或无符号的 https://en.wikipedia.org/wiki/C_data_types
当传递给isspace
时,bp
将被提升为整数。如果它是有符号的并且设置了高位,那么它将被符号扩展为负整数。这可能意味着它不是isspace
函数所需的无符号字符或 EOF https://linux.die.net/man/3/isspaceNo
请参阅 http://cpp.sh/9mp2i 了解它如何更改位位并更改该空间看到的值
如果我们假设char类型的位总是8,
那么这里带有0xff的代码按位和运算符会让我们感到困惑。
但是,如果char类型并不总是8位呢?
那么0xff可能有另一种含义,对吧?
实际上,char类型并不总是8位,我们可以在C99标准中看到详细信息。标准中的字符类型未定义为 8 位。
以下是C99标准如何描述字符类型的大小。
6.5.3.4 大小运算符 应用于类型为 char、无符号 char 或有符号 char(或其限定版本)的操作数时结果为 1。当应用于具有数组类型的操作数时, 结果是数组中的总字节数。当应用于 具有结构或联合类型的操作数,结果是总计 此类对象中的字节数,包括内部字节数和尾随字节数 填充。
6.2.5 类型声明为 char 类型的对象足够大,可以存储基本执行字符集的任何成员。如果会员 的基本执行字符集存储在 char 对象中,其 值保证为正数。如果存储了任何其他字符 在 char 对象中,结果值是实现定义的,但 应在可以表示的值范围内 类型。
例如 德州仪器的 TMS320C28x DSP 有一个 16 位的字符.
对于编译器在此处指定,CHAR_BIT第 99 页上为 16。
这似乎是一个现代处理器(目前正在销售),编译器支持C99和C++03。