《C陷阱与缺陷》读书笔记1
创始人
2024-01-18 22:38:08

在这里插入图片描述

词法分析:贪心法

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

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

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

例如:

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 
void f(int a){printf("%d",a);
} 
int main()
{		printf("%d %d %d",f,&f,*f);
}

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

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

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
阳澄湖在哪里哪个省的 阳澄湖是... 点击题目下方苏州生活指南有用 有趣 有态度近期的阳澄湖度很高,3月24日,央视二套《第一时间》直播连...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...