《C陷阱与缺陷》读书笔记1

时间:2022-11-03 20:53:03

《C陷阱与缺陷》读书笔记1

词法分析:贪心法

主要就是解释词法分析时的原则,即:

编译器将程序分解为符号时,从左到右一个字符接一个字符的读入。如果编译器的输入流截止至某个字符之前都已经被分解为一个个符号,那么下一个符号将包括从该字符之后可能组成符号的最长字符串。

就是说编译器也得一个字符接一个字符地读文本,但是读入的文本(也就是当前分析的符号)有歧义(有二义性)时,就按照能组成当前符号的最长字符串算。

例如:

int a = 3,b = 4;
a = a---b;
printf("%d %d",a,b);
// Output: -1 4

那么它读到---,就会解读为(a--) - b而不是a - (--b)

类似的问题还有:

a=-1可以被人理解为a =- 1a = -1

y=x/*p可以被人理解为y=x /* py=x / *p


说“被人理解为”是因为计算机表示这样理解,人的理解有二义性,但是计算机一定会遵循某种规则理解出唯一的含义

当然,学习的目的就是为了去消除二义性,一方面读到这种2b代码时知道该怎么理解(这种代码多出现在做题中),另一方面知道如何避免,(不难从上面看出解决方法)也就是善用分隔符,书写固定规范的代码。

整型常量

如果整型常量第一个字符是0,那么该常量将被视为八进制数

例如十进制141就是八进制的0215

int a = 0215,b = 4;
printf("%d %d",a,b);
// Output:141 4

函数声明

float ((f));

((F))返回类型是float,可推知f返回类型是float,亦即f为浮点型

float ff();

ff()求值结果是浮点数,ff()本身是个函数,即ff()是返回浮点值的函数。

float *g();

等价于float* g(),返回指向浮点数的指针的函数。按优先级()结合优先级高于*故先有g()为一个函数

()结合优先级高于*


然后复杂的就来了:

float (*h)();

h是个函数指针,它指向的函数返回值是float

推理过程:

h // 变量名
*h // 指针变量
(*h)() // 指向函数,由于优先级得给*h用括号围起来
float (*h)() // 明确函数的返回值

作为一个函数指针,首先它指向的得是函数,所以最右侧有(),然后函数的返回值毋庸置疑也是写在最左侧的。也就是float __ ()现在剩下下划线部分得继续填空。

// 这是一个函数指针的例子
// p的类型为 int(*)(int,int)
int(*p)(int, int);

那么进一步地,(float (*)())表示“指向返回值为浮点类型的函数的指针”的类型转换符。


难度增加:

(*(void(*)())0)();

毋庸置疑,这首先是一个函数调用,因为xxx();是函数调用最显而易见的形式。

调用的函数是哪个呢?得看最右侧括号左面的。显然(*(void(*)())0)()符合函数指针调用函数的样子,即(*fp)();,这是经由函数指针fp调用它所指向的函数,,ANSI C允许简写为fp()

由上,既然*fp表示它fp所指向的函数,那么类比过来*(void(*)())0也表示一个函数

进一步地,虽然*(void(*)())0没见过,但是*(float)0我们能理解:0是int型,然后给它强制转换为float,然后对它使用解引用运算符,也就是把0当做地址,解引用得到这个地址的内容。

所以说float规定了*(float)0是按float格式解引用得到数据,并且void(*)()float意思相近。那么void(*)()要将0这个地址转换为void(*)()这个类型(即指向返回值为void类型的函数的指针),函数指针解引用得到的就是函数本身。

换句话说,我们把0强制转化为了一个指针类型的数据。那么指针变量指向内容就是这个地址0。

如果在这个基础上,指针是函数指针,那么0就是函数的地址

在编译时系统就会为这个函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址

所以最终就是调用首地址为0的子例程。


显然,在使用typedef后能有效地简化代码,降低阅读难度:

typedef void (*funcptr)();

(*(funcptr)0)();
// 等价于(*(void(*)())0)();

书中的最后一个例子是取自signal.h,其中有这样一个函数声明:

void (*signal(int sig, void (*handler)(int)))(int);

意思是signal函数接受两个参数,分别是一个被捕获的信号整数值sig,另一个是指向由用户提供的函数的指针handlerhandler指向的函数接受一个int参数,返回值为void,用于处理捕获到的信号。

然后整个函数签名的理解就分为两部分,一部分是handler,另一部分是signal函数,分别来看:

// handler
void (*handler)(int);
// signal
void (*signal(...))(int);

其中...signal函数的参数类型,由于signal函数的定义长得像void f(int);这种,也不难发现对于signal(...)解引用应当得到一个函数,也就是说signal函数的返回值是一个指向返回值为void类型的函数的指针,这样才能进一步解引用得到一个函数。

函数signal具有参数时,其函数签名可能看起来比较奇怪,但是倒也不是无法理解:毕竟对void (*signal(...))(int);解引用才得到返回值是void且接受一个int的函数,这也就表明了signal函数的返回值。

使用typedef简化得:

typedef void (*HANDLER)(int);
HANDLER signal(int, HANDLER);

这样看就很明了了,signal函数的返回值是一个函数指针,它指向的函数无返回值,有一个int类型参数。

像是高阶函数。


再提一下,对于下列程序

#include <stdio.h>
void f(int a){
	printf("%d",a);
} 
int main()
{		
	printf("%d %d %d",f,&f,*f);
}

这里输出是一样的,函数名就是函数入口地址。

看了看为什么c语言中对函数名取地址和解引用得到的值一样这个问题