a---b 到底切成什么?C 编译器切符号的“贪心法“

【C语言】词法陷阱(四):符号的解析详解 在C语言编程中,符号的解析是词法分析的核心环节。编译器采用贪心法尽可能多地读取字符以形成完整符号,但这一过程易引发陷阱。若符号组成与预期不符,可能导致编译错误或未定义行为。此外,空白字符虽常用于分隔符号,但在字符串和字符常量内部却保留其意义,需特别留意。本文深入探讨C语言符号解析的陷阱与缺陷,旨在帮助开发者避免常见错误,提升代码质量。 阅读详情

参考:Andrew Koenig《C 陷阱与缺陷(第二版)》1.3节

目录

符号分两类

规则

a---b

y = x/*p

符号中间不能嵌空白

这条规则的历史

表格


符号分两类

书里先给符号分了类:

C 语言的某些符号,例如 /、*、+、=,只有一个字符长,称为单字符符号。而 C 语言中的其他符号,例如 /* 和 ==,以及标识符,包括了多个字符,称为多字符符号。

单字符符号没有歧义,一眼就能认出来。麻烦全在多字符符号上:

当 C 编译器读入一个字符 / 后又跟了一个字符 *,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。

/ 后面跟 *——它到底是“除号 + 解引用”,还是“注释开头”?

同一个字符序列,两种完全不同的读法。

C 语言的某些符号,例如 /、*、+、=,只有一个字符长,称为单字符符号。而 C 语言中的其他符号,例如 /* 和 ==,以及


标识符,包括了多个字符,称为多字符符号。

单字符符号没有歧义,一看就是一个。麻烦全在多字符符号上:

当 C 编译器读入一个字符 / 后又跟了一个字符 *,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。

/ 后面跟 *——它到底是"除号 + 解引用",还是"注释开头"?

同一个字符序列,两种完全不同的读法。


规则

C 语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。

书里紧接着给了完整过程:

字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;

如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。

这个处理策略有时被称为"贪心法",或者,更口语化一点,称为"大嘴法"。

贪心法这个名字还好理解一点,这个大嘴法是怎么叫出来的?

大嘴法 = 嘴张到最大,能吞多少就吞多少,吞不下才吐。​

词法分析器面对一串字符,规则只有一条:从当前位置开始,尽可能多地吃字符,直到再多吃一个就"不成词"为止,然后退回上一个成词的状态,把嘴里那段吐出来当一个符号。

同一段里,作者还引了 Kernighan 与 Ritchie (C语言的共同奠基人)的原文表述——

这是这本书里少见的"请出祖师爷":

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

最长字符,这件事就讲完了。下面看两个例子


a---b

书里的原话很干脆:

例如,== 是单个符号,而 = = 则是两个符号,下面的表达式 a---b 与表达式 a -- - b 的含义相同,而与 a - -- b 的含义不同。

按贪心法,a---b 是这样被一点点吃掉的:

a---b
  │
  ├─ 读到 a      → 标识符从这里开始
  ├─ 下一个是 -  → 标识符不能含 '-',所以 a 到此为止,切出符号 a
  ├─ 读到 -      → 再看下一个,也是 -,'--' 是合法符号(自减),继续吃
  ├─ 再看下一个  → 是 -,'---' 不是合法符号,退回,只吃 '--'
  └─ 剩下        → - b
结果:(a--) - b

实测

#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = a--- b;
	printf("%d %d", a, b);
	return 0;                                 
}
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = a- --b;
	printf("%d %d", a, b);
	return 0;                                 
}

两行的差值一模一样,但减的是不同的东西:第一段减了 a,第二段减了 b。

a、b 的变化是关键——只有它们显示了编译器到底切了哪个符号。


y = x/*p

接下来这个例子更直接。

同样地,如果 / 是为判断下一个符号而读入的第一个字符,而 / 之后紧接着 *,那么无论上下文如何,这两个字符都将被当作一个符号 /*,表示一段注释的开始。

无论上下文如何——这五个字是关键。词法分析器不看语义,它只看字符。

书里给的代码,本意是"用 x 除以 p 所指向的值":

y = x/*p    /* p 指向除数 */;

书里的判词:

而实际上,/* 被编译器理解为一段注释的开始,编译器将不断地读入字符,直到 */ 出现为止。

也就是说,该语句直接将 x 的值赋给 y,根本不会顾及到后面出现的 p。

书里给了两种修法:

y = x / *p    /* p 指向除数 */;
y = x/(*p)    /* p 指向除数 */;

符号中间不能嵌空白

同页还有一句容易漏掉的规则:

除了字符串与字符常量,符号的中间不能嵌有空白(空格符、制表符和换行符)。

也就是说 == 是一个符号,而 = = 是两个符号。

这对复合赋值运算符影响最大——+=、-=、>>= 必须连写,中间加一个空格就不再是那个符号了。


这条规则的历史

书里最后半页讲了个有意思的历史。

老版本 C 语言里,=+ 就是现在的 +=:

这种老版本的 C 编译器会将 a=-1;

理解为下面的语句 a =- 1;

亦即 a = a - 1;

因此,如果程序员的原意是 a = -1;,那么所得结果将使其大吃一惊。

同一段里还有一个如今已经不可能复现的例子:

另一方面,尽管 /* 看上去像一段注释的开始,在下例中这种老版本的编译器会将 a=/*b; 当作 a =/ *b ;

这种老版本的编译器还会将复合赋值视为两个符号,因而可以毫无疑问地处理 a >> = 1;,而一个严格的 ANSI C 编译器则会报错。

也就是说,老编译器把 >>= 看成 >> 和 = 两个符号,所以 a >> = 1 中间有空格也能过;

而今天的编译器按贪心法处理,>> 后遇到空格就断了,于是报错。

表格

写下的

编译器切出的

说明

a---b

(a--) - b

贪心吃到 -- 就停

a - --b

a - (--b)

空格切断了贪心

x/*p

x + 注释开始

/* 优先于 / 和 *

x / *p

除法 + 解引用

空格救回来

= =

两个符号

符号中间不能嵌空白

>>=

一个符号

今天的编译器只认这个

词法分析器不看编译者想要什么,只看"再多读一个字符还能不能构成合法符号"。

AD9026芯片开发实战指南:从硬件配置到射频校准 本文为AD9026芯片开发实战指南,详细解析了从硬件设计、电源时钟配置、SPI初始化到多芯片同步与JESD204高速链路建立的完整流程。重点探讨了射频校准技术,包括初始化校准与跟踪校准,以保障5G、MIMO等现代通信系统所需的射频性能。旨在帮助开发者系统掌握这颗高性能射频收发器的应用,规避常见设计陷阱。 阅读详情

相关推荐

定时移动鼠标点击鼠标,防止电脑息屏的vba脚本

定时移动鼠标,使系统始终处于激活状态,防止系统息屏。

panfei263031的博客 5927

c语言中各个符号的含义总结,C语言中的符号总结

1、注释符号       //和/* ...*/2、续行符号         \3、转义符号       常用:\r 和 \n 等4、单引号 ‘括起来的一个字符代表整数5、双引号 “括起来的字符代表一个指针单引号 ‘括起来的一个字符代表整数...

weixin_34061241的博客 3万+

【ESP32】ESP32连接JY61P并通过WIFI发送给电脑

ESP32S连接JY61P并通过WIFI把姿态数据发送给电脑1.采用Arduino IDE编译器;需要安装ESP32的开发板管理器;2.电脑接受数据是基于python的;

Doudou_Nai的博客 1130

a---b 到底切成啥?一文搞懂 C 编译器词法“贪心法/大嘴法”

C 编译器切符号用“贪心/最长匹配”:从左到右,能拼成更长合法 token 就继续拼,拼不动才收尾。a---b因此变成a-- - b;后紧跟变成注释开始;+++++之类即使词法能切,语义也常报错。写业务代码别考验编译器和同事的眼力,空格和括号是最便宜的 bug 防护。

2601_96633595的博客 126

LPDDR5X内存技术解析:性能提升与优化策略

内存技术在现代计算系统中扮演着核心角色,其性能直接影响整体系统效率。LPDDR5X作为JEDEC最新标准,通过提升数据传输率至8533Mbps、优化能效比15%以上,并扩展突发长度至32字节,显著提升了移动设备的处理能力。这些改进特别适用于5G通信、AI加速(如昇腾芯片)及8K视频处理等高带宽需求场景。技术原理上,LPDDR5X引入了动态电压频率调整(DVFS)和可编程突发长度等创新机制,有效降低了实际应用中的延迟。在工程实践中,合理的PCB布局和信号完整性验证是确保高速稳定运行的关键。随着AI和8K应用的

weixin_29190169的博客 395

(五)激光线扫描-位移台标定

线激光属于主动测量方式,但是由于线激光的特性,我们只能通过提取激光中心线获取这一条线上的高度信息,那么要进行三维重建的话,就需要通过平移或者是旋转的方式,来让线激光扫描被测物体的完整轮廓,也就是整个表面。激光线的密度越高还原出来的物体越细腻,但由于数据量大,也会导致速度变慢。下面以位移台为例讲解,如何标定位移台。

FlyWine的博客 2888

C 编译器判断符号贪心法

字符与符号判断的贪心法 C 语言中的某些符号/,*,=只有一个字符长,称为单字符符号。而其它符号==,/*包含了多个字符。 我们的编译器怎么去识别它们的呢? 编译器将程序分解成符号的方法是:从左到右一个一个字符地读入,如果该字符可以组成一个符号,那么再读入一下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组...

CCPigSnail 372

【ARM Coresight | AMBA BUS | Cache 与 MMU/MPU | CoreLink | GCC | CSH 专栏导读】

1. ARM Coresight(SoC-400/SoC-600) 专栏 2. ARMv8/v9 CPU/Cache 专栏 3. Linux ARM AMBA 总线 专栏 4. Trace32/DS-5 专栏。。。

CodingCos的博客 8239

a+++b C语言记法分析中的“贪心法”

转载自: http://6460646.blog.163.com/blog/static/2777987520092199332357/ C语言的某些符号,例如/   *  和=,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/*     ==  ,以及标识符等多字符符号。当C编译器读入一个字符'/'后又跟了一个字符 '*',那么编译器就必

Luyouzhen的博客 985

华为OD面试手撕真题 - 最大数

华为OD面试手撕真题 最大的数 提供Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解

qq_45776114的博客 337

【RT-DETR涨点改进】独家创新改进篇 | AAAI2024 | RT-DETR引入CFBlock增强对长程依赖和全局语义信息的建模能力,有助于提升复杂场景下小目标、遮挡目标的检测精度

本文给大家介绍在RT-DETR中引入CFBlock模块,能够有效提升模型对长程依赖和全局语义信息的建模能力,在保持轻量化和高效推理的基础上增强特征表达力,特别有助于提升复杂场景下小目标、遮挡目标的检测精度,从而实现更优的精度与速度平衡。

Ai缝合怪、助力小伙伴高效发论文 258

C 编译器的“贪心法”

C语言中有单字符符号和多字符符号之分,那么,当C编译器读入一个字符‘/’后又跟了一个字符‘*’,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。C语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经...

weixin_30340745的博客 194

基于Simulink的加速度计振动分析:单自由度质量-弹簧-阻尼系统

摘要:基于Simulink的加速度计单自由度振动分析 本文研究了加速度计的单自由度(SDOF)质量-弹簧-阻尼系统模型,通过MATLAB/Simulink平台建立了完整的振动分析仿真系统。文章详细阐述了理论建模过程,推导了系统运动微分方程和传递函数,并提供了Simulink模型搭建的具体步骤和MATLAB实现代码。研究通过正弦扫频、随机振动和冲击响应三种典型输入信号,分析了系统的幅频特性、相频特性及时域响应,验证了加速度计的工作机理。仿真结果表明,该方法能有效模拟加速度计在不同激励条件下的动态特性,为传感器

AI的内部世界的博客 162

《C陷阱与缺陷》词法陷阱-贪心法

C陷阱与缺陷中的贪心法

QJing_shijia的博客 273

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C 词法分析中的“贪心法”

c语言底层读取字符的方法: C语言的某些符号,例如/、*、和=,只有一个字符长,称为单字符符号。而C语言中的其他符号,例如/“和==,以及标识符,包括了多个字符,称为多字符符号。当C编译器读入一个字符后又跟了一个字符“,那么编译器就必须做出判断: 是将其作为两个分别的符号对待,还是合起来作为一个符号对待。 C语言对这个问题的解决方案可以归纳为一个很简单的规则;每一个符号应该包含尽可能多的字符。也就...

共勉 774

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

C语言,词法分析的“贪心法”

C语言中,经常会有很变态的语法表达,例如 a—b;该如何解释?但凡碰到这种情况,加括号就可以完全解决。无奈,面试中,却经常会有这样的题目出现。在阅读《C Traps and pitfall》中,提到了一种分析方法,”贪心法”,可以一劳永逸的解决类似这种问题。

iemink的专栏 1686

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

C语言经典错误总结(一)

|优先级大于=优先级(逻辑运算符优先级大于赋值运算符优先级),所以该式子:c = ' ' || c == '\t' || c == '\n'是将 ' ' || c == '\t' || c == '\n'表达式结果赋值给c,然后判断是否为真,是真就继续下循环,因此出现无限循环的情况。本意是将x除以指针p指向的值,再把所得的商赋值给y,结果写成了第一行的代码,显然,问题大了,在编译器中看到/*就会被认为代码注释的开始,直到找到*/结束,所以我们因该写成第二行。最后,希望大家C语言学习犯错越来越少,加油!

2301_79813267的博客 3081

《C陷阱与缺陷》----词法“陷阱”

研究一种语言中程序员容易犯错误之处,不但可以“前车之覆,后车之鉴”,还能使我们更谙熟这种语言的深层运行进制,做到“运用之妙,存乎一心”。

Extreme_wei的博客 1687

初识C词法分析“贪心法”

编译器将程序分解符号的方法是,从左到右一个字符一个字符的读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已经不可能再组成个有意义的符号。这个处理策略就是“贪心法”。贪心法:每一个符号应该尽可能的包含多的字符。例如上述表达式利用贪心法分析就是。但这个表达式是没有意义的。

fengQQAN的博客 166

关于C编译器词法分析中的“贪心法”

我们都知道,C语言中有单字符符号和多字符符号之分,那么,当C编译器读入一个字符‘/’后又跟了一个字符‘*’,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。C语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字...

奇境 541

【C语言每日亿题】运算符专练 · 第3日

从今天开始,我将正式开启一个新的打卡专题——《C语言百炼成神计划》,没错!百炼成神,目的是通过百天刷题计划,通过题目和知识点串联的方式,完成C语言的复习和巩固;后期还会配有专门的笔记总结和文档教程哦!想要搞定,搞透C语言的同学欢迎持续关注!.........

kikokingの比特宇宙 1万+

C语言的贪心法(大嘴法)

当C编译器读入了一个字符’/‘后又读入了一个字符’*’,那么编译器就必须做出判断:是将其作为两个分别的符号对待,还是合起来作为一个符号对待。 C语言中,每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个符号,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。这个处理策略有时被称为“贪心法”,或

shanhejieshiwo的博客 484

C陷阱与缺陷(一)之词法分析中的“贪心法”

词法分析中的“**`贪心法`**”:C语言中的某些符号,例如/、*、和=,只有一个字符长, 称为单字符符号,但是例如/*、==包括了多个字符,称为多字符符号。当C编译器读入了'/'又读入了'*',那么编译器是将两个字符作为不同符号对待还是合起来作为一个符号呢? 答案是合起来,C语言对这个问题的解决方案可以归纳为一个很简单的规则:每一个符号应该包含尽可能多的字符。即“贪心法”“大嘴法” 例1:a...

qian_xia_er的博客 315

C语言符号的贪心法则

贪心法 C语言有这样一个法则 : 买一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左向右一个一个字符的读入,如果该字符可能组成一个符号,那么在读入秀一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判读,知道读入的字符组成的字符已不再可能组成一个有意义的符号。这个处理的策略称为“贪心法”。需要注意的是,除了字符串与字符常量,符号的中间不能嵌有空白(空格、制表符、换行符)比如 == 是单个符号, = = 是两个符号。

m0_53994544的博客 374

C陷阱与缺陷 第1章 词法“陷阱” 1.3 词法分析中的“贪心法”

C陷阱与缺陷 第1章 词法“陷阱” 1.3 词法分析中的“贪心法”

weixin_客子光阴的博客 251
上一篇: else 到底配对哪个 if?C 语言「悬挂 else」的缩进骗局
下一篇: #define max(a,b) 的bug频出?宏为什么不是函数
UIU114
博客等级 码龄111天 62粉丝 90原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值