C指向按位和运算符的数组声明的指针


9

我想了解以下代码:

//...
#define _C 0x20
extern const char *_ctype_;
//...
__only_inline int iscntrl(int _c)
{
    return (_c == -1 ? 0 : ((_ctype_ + 1)[(unsigned char)_c] & _C));
}

它源自obenbsd操作系统源代码中的文件ctype.h。此函数检查char是ascii范围内的控制字符还是可打印的字母。这是我目前的思路:

  1. 调用iscntrl('a')并将'a'转换为其整数值
  2. 首先检查_c是否为-1,然后返回0,否则...
  3. 将未定义指针指向的地址加1
  4. 声明此地址为长度数组的指针(unsigned char)((int)'a')
  5. 将按位和运算符应用于_C(0x20)和数组(???)

奇怪地,它以某种方式起作用,并且每次返回0时,给定的char _c都不是可打印字符。否则,当该函数可打印时,它只会返回一个没有特殊意义的整数值。我的理解问题在于第3步,第4步(有点)和第5步。

感谢您的任何帮助。


1
_ctype_本质上是位掩码的数组。它被感兴趣的字符索引。因此_ctype_['A']将包含与“ alpha”和“大写” _ctype_['a']相对应的位,将包含与“ alpha”和“小写” _ctype_['1']相对应的位, 将包含与“ digit”相对应的位,依此类推。看起来0x20是与“ control”相对应的位。但是由于某种原因,_ctype_数组偏移了1,所以for的位'a'实际上在中_ctype_['a'+1]。(这可能EOF即使没有额外的测试也可以使其正常工作。)
史蒂夫·萨米特

强制转换(unsigned char)为要考虑字符签名和否定的可能性。
史蒂夫·萨米特

Answers:


3

_ctype_似乎是符号表的受限制的内部版本,我猜+ 1是因为它们没有0可打印性,所以他们没有理会它的索引。或者可能他们使用1索引表而不是C中的自定义0索引表。

对于所有ctype.h函数,C标准都规定了这一点:

在所有情况下,参数均为an int,其值应表示为an unsigned char或等于宏的值EOF

逐步检查代码:

  • int iscntrl(int _c)这些int类型实际上是字符,但是所有ctype.h函数都需要处理EOF,因此必须为int
  • 支票兑现-1是支票EOF,因为它具有价值-1
  • _ctype+1 是用于获取数组项地址的指针算法。
  • [(unsigned char)_c]只是对该数组的数组访问,在其中进行强制转换以强制将参数表示为的标准要求unsigned char。请注意,char实际上可以保持负值,因此这是防御性编程。[]数组访问的结果是其内部符号表中的单个字符。
  • &那里的遮罩可从符号表中获取特定的字符组。显然,所有设置了第5位(掩码0x20)的字符都是控制字符。不查看表就没有任何意义。
  • 设置了第5位的任何内容都将返回被0x20屏蔽的值,该值是非零值。这满足了在布尔值为true的情况下函数返回非零的要求。

强制转换标准要求该值可表示为,这是不正确的unsigned char。该标准要求在调用例程时,该值已经*可表示为unsigned char,或等于EOF。强制转换仅用作“防御性”编程:纠正程序员的错误,该程序员在负有责任的情况下传递带符号的char(或signed char),而unsigned char在使用ctype.h宏时传递值。应当注意,如果char在使用-1的实现中传递-1 的值,则无法纠正错误EOF
Eric Postpischil

这也提供的解释+ 1。如果宏之前未包含此防御性调整,则它可能仅实现为((_ctype_+1)[_c] & _C),因此具有一个索引表,其中索引有预调整值-1至255。因此,没有跳过第一个条目,并且确实可以达到目的。当以后有人添加防御型演员表时,EOF-1 的值不适用于该演员表,因此他们添加了条件运算符来对其进行特殊处理。
Eric Postpischil

3

_ctype_是指向257个字节的全局数组的指针。我不知道用了什么_ctype_[0]_ctype_[1]到分别_ctype_[256]_表示字符0,…,255的字符类别:_ctype_[c + 1]表示字符的类别c。这与_ctype_ + 1指向256个字符的数组((_ctype_ + 1)[c]表示字符的类别)的说法相同c

(_ctype_ + 1)[(unsigned char)_c]不是声明。它是使用数组下标运算符的表达式。它(unsigned char)_c以开头的数组的访问位置(_ctype_ + 1)

该代码铸件_cintunsigned char不是绝对必要的:ctype函数需要转换为字符值unsigned charcharOpenBSD上签字):正确的呼叫char c; … iscntrl((unsigned char)c)。它们的优点是可以确保没有缓冲区溢出:如果应用程序iscntrl使用的值在unsigned char-1 的范围之外且不为-1,则此函数返回的值可能无意义,但至少不会导致恰好在数组范围之外的地址上的崩溃或私有数据泄漏。char c; … iscntrl(c)只要c不为-1,则调用该函数的值甚至正确。

-1的特殊情况的原因是EOFchar例如getchar,许多对C进行操作的标准C函数将字符表示为一个int值,该值是包装在正数范围内的char值,并使用特殊值EOF == -1指示无法读取任何字符。对于像功能getcharEOF指示文件的结束,因此得名ë ND- ø F- ˚F ILE。Eric Postpischil建议该代码最初只是return _ctype_[_c + 1],这可能是正确的:_ctype_[0]将是EOF的值。如果该函数被滥用,则这种更简单的实现会导致缓冲区溢出,而如上所述,当前的实现避免了这种情况。

如果v是在数组中找到的值,则v & _C测试中的位0x20是否设置为v。数组中的值是字符所在类别的掩码:_C为控制字符_U设置,为大写字母设置等。


(_ctype_ + 1)[_c] 使用C标准指定的正确数组索引,因为用户有责任传递EOFunsigned char值。C标准未定义其他值的行为。强制转换不能实现C标准所需的行为。这是一种变通方法,可防止程序员错误地传递负字符值而导致错误。但是,它不完整或不正确(并且无法更正),因为-1字符值将必须视为EOF
埃里克·波斯特皮希尔

这也提供的解释+ 1。如果宏之前未包含此防御性调整,则它可能仅实现为((_ctype_+1)[_c] & _C),因此具有一个索引表,其中索引有预调整值-1至255。因此,没有跳过第一个条目,并且确实可以达到目的。当以后有人添加防御型演员表时,EOF-1 的值不适用于该演员表,因此他们添加了条件运算符来对其进行特殊处理。
埃里克·波斯特皮希尔

2

我将从步骤3开始:

未定义指针指向的地址加1

指针不是未定义的。它只是在其他一些编译单元中定义的。那就是extern告诉编译器的内容。因此,当所有文件链接在一起时,链接器将解析对该文件的引用。

那么它指向什么呢?

它指向一个数组,其中包含有关每个字符的信息。每个字符都有其自己的条目。条目是字符特征的位图表示。例如:如果设置了位5,则表示该字符是控制字符。另一个示例:如果设置了位0,则表示该字符是高位字符。

因此,类似的东西(_ctype_ + 1)['x']将获得适用于的特征'x'。然后按位执行并检查是否设置了位5,即检查它是否是控制字符。

添加1的原因可能是实数索引0保留用于某些特殊目的。


1

这里的所有信息都是基于对源代码(和编程经验)的分析。

报关单

extern const char *_ctype_;

告诉编译器有一个指向const char某个地方命名_ctype_

(4)该指针作为数组访问。

(_ctype_ + 1)[(unsigned char)_c]

强制转换(unsigned char)_c确保索引值在unsigned char(0..255)范围内。

指针算术_ctype_ + 1有效地将数组位置移动了1个元素。我不知道他们为什么以这种方式实现数组。使用范围_ctype_[1].. _ctype[256]为字符值0.. 255叶值_ctype_[0]未使用此功能。(偏移量1可以通过几种替代方式实现。)

数组访问char使用字符值作为数组索引来检索一个值(类型为,以节省空间)。

(5)按位与运算从值中提取单个位。

显然,来自数组的值用作位字段,其中位5(从0开始,至少是有效位= 0x20)是“是控制字符”的标志。因此,数组包含描述字符属性的位字段值。


我猜他们将移到了+ 1指针,以明确表示它们正在访问元素,1..256而不是1..255,0_ctype_[1 + (unsigned char)_c]由于将隐式转换为,因此将是等效的int。而且_ctype_[(_c & 0xff) + 1]本来会更加清晰和简洁。
cmaster-恢复莫妮卡

0

这里的关键是了解表达式的(_ctype_ + 1)[(unsigned char)_c]作用(然后将其馈入按位运算),& 0x20以获取结果!

简短的答案:返回_c + 1由指向的数组元素_ctype_

怎么样?

首先,尽管你似乎认为_ctype_未定义其实不然!标头将其声明为外部变量-但是(几乎可以肯定)它是在构建程序时与程序链接到的一个运行时库中定义的。

为了说明语法与数组索引的对应关系,请尝试(甚至编译)以下简短程序:

#include <stdio.h>
int main() {
    // Code like the following two lines will be defined somewhere in the run-time
    // libraries with which your program is linked, only using _ctype_ in place of _qlist_ ...
    const char list[] = "abcdefghijklmnopqrstuvwxyz";
    const char* _qlist_ = list;
    // These two lines show how expressions like (a)[b] and (a+1)[b] just boil down to
    // a[b] and a[b+1], respectively ...
    char p = (_qlist_)[6];
    char q = (_qlist_ + 1)[6];
    printf("p = %c  q = %c\n", p, q);
    return 0;
}

随时要求进一步的澄清和/或解释。


0

在中声明的函数ctype.h接受类型的对象int。对于用作参数的字符,假定它们已预先转换为类型unsigned char。该字符用作确定字符特征的表中的索引。

似乎在包含的值_c == -1时使用了检查。如果不是,则将_c强制转换为无符号字符类型,该字符类型用作表达式指向的表中的索引。如果设置了掩码指定的位,则该字符为控制符号。_cEOFEOF_ctype_ + 10x20

了解表达

(_ctype_ + 1)[(unsigned char)_c]

考虑到数组下标是一个后缀运算符,其定义如下

postfix-expression [ expression ]

你可能不会这样写

_ctype_ + 1[(unsigned char)_c]

因为此表达式等效于

_ctype_ + ( 1[(unsigned char)_c] )

因此,将表达式_ctype_ + 1括在括号中以获得一个主表达式。

所以事实上你有

pointer[integral_expression]

产生索引处的数组对象,该数组的索引被计算为表达式integral_expression,其中pointer是(_ctype_ + 1)(gere用于指针arithmetuc)integral_expression,而index是expression (unsigned char)_c

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.