8

我正在阅读一些实现简单解析器的代码。一个名为的函数将scan一行分解为标记。scan有一个静态变量bp,该变量分配了要标记的行。在赋值之后,空白被跳过。见下文。我不明白的是为什么代码会按位执行bp指向 with的字符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;

        ..
}
4

6 回答 6

7

来自 C 标准(7.4 字符处理 <ctype.h>)

1 标题 <ctype.h> 声明了几个对字符分类和映射有用的函数。198)在所有情况下,参数都是一个 int,其值应表示为无符号字符或应等于宏 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 的值),则函数的行为未定义。

于 2021-05-24T19:50:37.780 回答
3

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转换为。让我们假设例如并假设一个具有 8 位 char 且已签名且具有 32 位的健全平台,然后:intisspace*bp = -42int

*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]是无效的。

于 2021-05-24T19:39:20.820 回答
1

你的问题:

这怎么样:

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

与此不同:

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

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

简而言之,这是一个健全性检查,以确保isspace只比较bp变量中的一个字节。

于 2021-05-24T19:23:02.967 回答
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; 

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

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

while (isspace((unsigned char)(*bp)))
    ++bp;
于 2021-05-24T19:31:33.860 回答
1

在 c 中,char 可以签名或未签名https://en.wikipedia.org/wiki/C_data_types

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

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

于 2021-05-24T19:46:01.193 回答
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_BIT 为 16(第 99 页)。

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

于 2021-06-18T05:50:41.040 回答