C陷进与缺陷
其他
2019-06-16 12:04:38
阅读次数: 0
1、词法分析
C语言的符号(token)
之间的空白
包括空格符
、制表符
和换行符
。
编译器
将程序分解成符号(token)
的方法是:从左到右
一个字符一个字符地读入,如果该字符可能组成一个符号
,那么再读入下一个字符,判断已经读入的两个字符组成的字符串
是否可能是一个符号的组成部分
;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串
已不再可能组成一个有意义的符号
。
可以归纳为一个很简单的规则:每一个符号(token)
应该包含尽可能多的字符
。这个处理策略被称为贪心法
。
2、字符与字符串
用双引号
引起的字符串
,代表的是一个指向无名数组起始字符
的指针
,该数组被双引号之间的字符
以及一个额外
的二进制值为0
的字符'\0'
初始化。
3、理解函数声明:
当计算机启动时,硬件将调用首地址为0位置
的子例程
:
( *(void(*)()) 0)();
任何C变量
的声明
都由两部分
组成:类型
以及一组类似表达式
的声明符(declarator)
。声明符
从表面上看与表达式
有些类似,对它求值
应该返回声明
中给定类型的结果
。
float *g(), (*h)();
表示*g()
与(*h)()
是浮点表达式
。因为()
结合优先级
高于*
,*g()
也就是*(g())
:g
是一个函数
,该函数的返回值类型
为指向浮点数
的指针
。
同理,可以得出h
是一个函数指针
,h
所指向函数
的返回值
为浮点类型
。
一旦我们知道了如何声明
一个给定类型
的变量
,那么该类型的类型转换符
就很容易得到了:只需要把声明中的变量名
和声明末尾
的分号
去掉,再将剩余的部分用一个括号
把整个封装
起来即可。例如下面的声明:
float (*h)();
表示h
是一个指向返回值为浮点类型的函数的指针
,因此,
(float (*)())
表示一个指向返回值为浮点类型的函数的指针
的类型转换符
。
拥有了这些预备知识,我们现在可以分两步来分析表达式( *(void(*)()) 0)()
。
第一步,假定变量fp
是一个函数指针,调用fp
所指向的函数方法如下:
(*fp)();
因为fp
是一个函数指针
,那么*fp
就是该指针所指向的函数
,所以(*fp)()
就是调用该函数
的方式。ANSI C
标准允许程序员将上式简写为fp()
,但是一定要记得这种写法只是一种简写形式
。
在表达式(*fp)()
中,*fp
两侧的括号非常重要,因为函数运算符()
的优先级高于单目运算符*
。如果*fp
两侧没有括号,那么*fp()
实际上与*(fp())
的含义完全一致,ANSI C
把它作为*((*fp)())
的简写形式。
现在,剩下的问题就只是找到一个恰当的表达式
来替换fp
。我们将在分析的第二步来解决这个问题。如果C编译器
能够理解我们大脑中对于类型的认识,那么我们可以这样写:
(*0)();
上式并不能生效,因为运算符*
必须要一个指针
来做操作数
。而且,这个指针还应该是一个函数指针
,这样经运算符*
作用后的结果才能作为函数被调用
。因此,在上式中必须对0
做类型转换
,转换后的类型可以大致描述为:指向返回值为void类型的函数的指针
。
如果fp
是一个指向返回值为void类型的函数的指针
,那么(*fp)()
的值为void
,fp
的声明如下:
void (*fp)();
因此,我们可以用下式来完成调用存储位置为0
的子例程
:
(*fp)();
我们一旦知道如何声明一个变量
,也就自然知道如何对一个常数
进行类型转换
,将其转型为该变量的类型
:只需要在变量声明
中将变量名去掉
即可。因此,将常数0
转型为指向返回值为void的函数的指针
类型,可以这样写:
(void (*)())0
因此,我们可以用(void (*)())0
来替换fp
,从而得到:
( *(void (*)()) 0)();
转载自blog.csdn.net/Hongwei_1990/article/details/89482355