【C 陷阱与缺陷 学习笔记】(一)词法陷阱

Souleigh ✨ 等级 424 0 0

一 内容

0. =不同于==

当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。

1.本意是检查 x 与 y 是否相等:

if(x = y)
    break; 

实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。

2.本意是跳过文件中的空白字符:

while(c = '' || c == '\t' || c == '\n')
    c = getc(f); 

因为 ' '不等于 0 (' '的 ASCII 码值为 32),那么无论变量为何值,上述表达式求值的结果都为 1,因此循环将进行下去直到整个文件结束。

C 编译器发现形如 x = y 的表达式出现在选择语句,循环语句的条件判断部分时,会给出警告。当确实需要对变量进行赋值时,为了避免警告,我们应该这样处理:

if((x = y) != 0)
    foo(); 

如果将赋值写成了比较,也会造成混淆:

if((filedesc == open(argv[i], 0)) < 0)
    error(); 

本例中,open 执行成功返回非零值,失败返回 -1。本意是将 open 函数的返回值存储在变量 filedesc 中,然后将其和 0 比较大小,判断 open 执行是否成功 。==运算符的结果只可能是 1 或 0,永远不会小于 0,所以 error() 将没有机会被调用。

1. &|不同于&&||

比较 i & ji && j ,只要 i 和 j 是 0 或 1 ,两个表达式的值是一样的(||| 同理。)。然而,一旦 i 和 j 的值为其他,两个表达式的值不会始终一致。

另一个区别是操作数带有自增自减的运算:

i & j++, j 始终会自增;但是 i && j++ 有时 j 不会自增。

2. 词法分析中的“贪心法”

当 C 的编译器读入一个字符/后跟着一个字符*时,那么编译器就必须做出判断:时将其作为两个符号对待,还是合起来作为一个符号对待。这类问题的规则:每个符号应该包含尽可能多的符号

例如:a---b(a--) - b含义相同,而与a - (--b)含义不同。

又如:下面的语句本意是 x 除以 p 指向的值然后将结果赋值给 y

y = x/*p; 

但是,实际上 /*被编译器理解为一段注释的开始。

将上面的语句重写如下:

y = x / *p; 

或者:

y = x/(*p); 

老版本的编译器允许使用=+来代表现在+=的含义,这种编译器会将:

a=-1; 

理解为:

a =- 1; 

即为:

a = a - 1; 

因此,如果程序员的原意为:

a = -1; 

那么结果会让其大吃一惊。

再如:

a=/*b; 

在老版本的编译器会将其当作:

a =/ *b; 

3. 整型常量

许多编译器会把 8 和 9 作为把八进制的数字处理,这种处理方式来源于八进制数的定义。例如:0195 的含义是1x8^2 + 9x8 + 5x8^0也就是 141(十进制)或 0215(八进制)。ANSI C 标准中禁止这种用法。

4. 字符与字符串

单引号引起的一个字符实际上代表一个整数。整数值对应于该字符在编译器采用的字符集中的序列值。因此,对于采用 ASCII 字符集的编译器而言,'a'的含义与 97 (十进制)严格一致。

用双引号引起的字符串,代表的确实一个指向无名数组起始字符的指针。该数组被双引号之间的字符以及一个额外的二进制值为 0 的字符\0初始化。

比如,下面的这个语句:

printf("Hello World\n"); 

等价于:

char hello[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd', '\n', 0};
printf(hello); 

整数型(一般为 16 或 32 位)的存储空间可以容纳多个字符(一般为 8 位),因此有的编译器允许在一个字符常量(以及字符串常量)中包含多个字符。也就是说:用'yes'代替"yes"不会被该编译器检测到。前者的含义大多数编译器理解为一个整数值,由'y','e','s'所代表的整数值按照特定编译器实现中的定义方式组合得到。

二 练习

练习 1

某些 C 编译器允许嵌套注释。请写一个测试程序,要求:无论编译器是否允许嵌套注释,该程序都能正常通过编译,但是两种情况下程序执行结果不同。

对于符号序列:

/*/**/"*/" 

如果允许嵌套注释,上面的符号序列表示:一个单独的双引号",因为最后的注释符前出现的符号都会被当作注释的一部分。

如果不允许嵌套注释,上面的符号就表示一个字符串:"*/"

Doug Mcllroy 发现了下面这个令人拍案叫绝的解法:

/*/*/0 */**/1 

这个解法主要利用了编译器作词发分析时的“贪心法”规则。

如果编译器允许嵌套注释,则将上式解释为:

/* /*/0 */ * */ 1 

上式的值为 1

如果编译器不允许嵌套注释,则解释为:

/* / */ 0 * /**/ 1 

也就是 0*1,值为 0

练习 2

a+++++b 的含义是什么?

上式唯一有意义的解析方式就是:

a++ + ++b 

可是,根据“贪心法”的规则,上式应该被解释为:

a++ ++ + b 

等价于:

(a++)++ + b; 

但是 a++的值不能作为左值,因此编译器不会接受 a++ 作为后面 ++ 运算的操作数。

参考资料《C 缺陷与陷阱》


以上就是本次的内容,感谢观看。

如果文章有错误欢迎指正和补充,感谢!

收藏
评论区

相关推荐

C++概述
概述 C 是静态,可编译,通用,大小写敏感,格式自由的编程语言,它支持程序化,面向对象的,和泛型编程方式。 C 被看作是中间层语言,因为它同时包含了低级语言和高级语言的特性。 C 是于 1979 年在新泽西的茉莉山丘的贝尔实验室由 Bjarne Stroustrup 开发的,它是 C 语言的加强版,最开始它被称作 “C with Classes”,但是
C++ 基本语法
C 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 对象具有状态和行为。例如:一只狗的状态 颜色、名称、品种,行为 摇动、叫唤、吃。对象是类的实例。 类 类可以定义为描述对象行为/状态的模板/蓝图。 方法 从基本上说,一个方法表示一种行为。一个类可以包含多个
【C 陷阱与缺陷 学习笔记】(一)词法陷阱
一 内容 0\. 不同于 当程序员本意是作比较运算时,却可能无意中误写成了赋值运算。 1.本意是检查 x 与 y 是否相等: c if(x y) break; 实际上是将 y 的值赋值给了 x ,然后再检查该值是否为 0 。 2.本意是跳过文件中的空白字符: c while(c '' || c '\t' ||
Swift与Objective-C混合编程之Swift与Objective-C API映射
原创文章,欢迎转载。转载请注明:关东升的博客 Swift与ObjectiveC API映射 在混合编程过程中Swift与ObjectiveC调用是双向的,由于不同语言对于相同API的表述是不同的,他们之间是有某种映射规律的,这种API映射规律主要体现在构造函数和方法两个方面。 1、构造函数映射 在Swift与ObjectiveC语言进行混合
统计字符串中字符出现的次数(Python版)
字符串转list python s 'aabbccd' list1 list(s) 方法一: python list1 'a', 'a', 'b', 'c', 'c', 'c', 'c' dict_cnt {} for value in list1: dict_cntvalue dict_cnt.get(value,
c++11 实现单例模式
C11出来后,里面新增加了好多好用的功能 下面的单例就是使用了C11中的标准库中的mutex和unique_prt 进行内存管理的. 此单例模式不用担心内存的释放问题 pragma once include <memory include <mutex template <class T class Singleton { public: ty
C语言_练习题(一)
前言: 看懂理解代码很容易,难的是把所理解的融会贯通,融合到实例中,你会发现事实和理论会有些许差别,编写实例能更好的帮你积累经验。 0x1 编写一个程序,要求提示输入一个ASCII码值(如,66),然后打印输入的字符。 代码: include <stdio.h int main(){ char i; printf("请输入一个ASCI
我的C语言基础
C语言32个关键字auto 声明自动变量short 声明短整型变量或函数int 声明整型变量或函数long 声明长整型变量或函数float 声明浮点型变量或函数double 声明双精度变量或函数char 声明字符型变量或函数struct 声明结构体变量或函数union 声明共用数据类型enum 声明枚举类型typedef 用以给数据类型取别名co
C语言入门系列之2.数据类型、运算符和表达式
一、数据类型C语言常见数据类型如下: 1.数据类型 基本数据类型基本数据类型最主要的特点是,其值不可以再分解为其他类型。也可以说,基本数据类型是自我说明的。 构造数据类型构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或
C语言基础习题50例(一)1-5
虎为百兽尊,罔敢触其怒。惟有父子情,一步一回顾。 习题1 有 1 、 2 、 3 、 4 个数字,能组成多少个互不相同且无重复数字的三位数?都是多少?实现思路:显然,这个题目需要用到循环,并且是循环嵌套,先列出所有可能的组合,再去掉重复的组合即可。代码如下:cinclude <stdio.hint main(){ int i, j, k,
C语言基础习题50例(二)6-10
给大家推荐一门大数据Spark入门课程,希望大家喜欢。 习题6 用 号输出字母C的图案。实现思路:单行打印即可。代码如下:cinclude <stdio.h int main (void){ printf("\n"); printf("\n"); printf("\n"); printf("
C语言基础习题50例(三)11-15
你们看出神马了吗(\\^_\^\) 习题11 有一对兔子,从出生后第 3 个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少实现思路:从第1个月起,兔子对数分别为1、1、2、3、5、8、13、21...,显然是斐波拉契数列。代码如下:cinclude<stdio.hint mai
C语言基础习题50例(四)16-20
给大家介绍一堂Python入门课,感觉还不错,适合初学者入门。 习题16 输入两个正整数 m 和 n ,求其最大公约数和最小公倍数。实现思路:求两个数的最大公约数分别采用辗转相除法、辗转相减法、枚举法得到,最小公倍数用两个数之积除以最大公约数即可获得。方式一——辗转相除法:思路:(1)将两整数求余 a%b x;(2)如果x 0;则b为最大公
游戏安全实践的一些思考
移动的游戏能够稳定健康的上线。主要需要依赖以下在四个方面:1.前端展示,或者说客户端正常运行。性能稳定不崩溃,不过热能够稳定运行。2.后端,或者游戏后台服务端的。不但要稳定。还有能在有限的服务器资源下,能承受大量的同时在线用户。而且要让游戏中的每个模块都能够承受承受大量的同时在线用户。3.安全也是重点之中。这既包括客户端,又包括服务端。客户端的安全,包括要防
IO编程实例——使用缓冲流实现文件的拷贝
数据源:"C:\\Users\\你是小朱老师呀\\Desktop\\test.txt"数据的目的地: "C:\\Users\\你是小朱老师呀\\Desktop\\XSC\\test.txt"实现步骤:1.创建源文件与目标文件2.创建节点流3.创建缓冲流4.读取、写入5.释放 package person.xsc.praticeIII;import java.