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

简介: 由于在C语言中赋值操作相对于比较出现更加频繁,所以将字符较少的符号=赋予更常用的含义—赋值操作。

导言:


由于一个程序错误可以从不同层面采用不同方式进行考察,而根据程序错误与考察程序的方式之间的相关性,可以将程序错误进行划分为各种陷阱与缺陷:

①.词法“陷阱”

②.语法“陷阱”

③.语义“陷阱”

④.连接问题

⑤.库函数问题

⑥.预处理器问题

⑦.可移植性缺陷

我们知道程序是由符号序列所组成的。本篇主要考察在程序被词法分析器分解成各个符号的过程中可能出现的问题,也就是词法“陷阱”。


1.=不同于==


符号 =作为赋值运算符


符号==作为比较运算符


由于在C语言中赋值操作相对于比较出现更加频繁,所以将字符较少的符号=赋予更常用的含义—赋值操作。


不过这种便利的使用可能导致一个潜在的问题:当程序员本想使用比较运算符却使用了赋值运算符。


1.1 案例1


if(x=y)
break;


该句的本意是想检查x是否等于y;但实际上却是将y的值赋给了x,然后检查表达式结果是否为0。

1.2 案例2


再比如下面这个例子:

例子中循环语句本想跳过空格符,制表符,和换行号;

   while(i=' '||i=='\t'||i=='\n')
   i=getchar();


但是却把比较运算符误写成赋值运算符,因为赋值运算符=的优先级是低于逻辑运算符||,所以语句先将’ ‘赋给了i,然后发先i结果不为0(’ 'ASCII码值是32)根据短路求值原则,左边为真,则右边不再进行,此语句也就是真,因此循环将一值进行下去直到整个文件结束。或者变成一个死循环。

1.3 启示


某些C编译器在发现形如a=b的表达式出现在循环语句的条件判断部分时,会给出警告以提醒程序员。当确实需要对变量进行赋值并检查该变量的新值是否为0时,为了避免该类编译器的警告,我们应该进行显式地比较,比如


if(x=y)
{
  Function();
}


应该写成


if((x=y)!=0)
{
  Function();
}


这样的写法就使得代码的意图一目了然。

1.4 案例3


前面一直谈的是将比较运算符误写成赋值运算符,另一方面,如果把赋值运算符误写成比较运算符,也会造成混淆。

if ((i == fun(a, b)) < 0)
    error();


本语句的原本意思是如果fun函数执行成功则返回0,执行失败则返回-1或者正数,然后再将返回值赋给i,通过比较i的大小来确定是否fun函数执行成功。但实际上是fun函数的返回值与i进行比较,结果只有真与假也就是0或1,永远不可能小于0,所以函数error没有机会调用。也就不知道fun函数调用是否失败。

2.&和 |不同于&&和 ||


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


C语言有许多符号,比如只有一个字符长的单字符符号,和多个字符长的多字字符。比如== 和 /*。

当C编译器读取符号时,是如何读取的呢?

比如读入一个字符’/‘后又跟了一个字符’ *',那编译器是将其作为两个分别的符号对待,还是合起来作为一个符号对待?

3.1 读取规则—“贪心法”:


每个符号应该包含尽可能多的字符。


也就是说,编译器将程序分解成符号的方法是:


从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成字符串不再可能组成一个有有意义的符号。


这个处理策略就被称为“贪心法”。

3.2 注意:


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


比如:

a---b 与表


3.3 “贪心法”读取案例1


本代码想表达的意思是,x先除以指针p指向的数,然后再将所得的商,赋给y.


y=x / *p;// *p指向的是被除数


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


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


而正确的表达意思应该是这样:


y=x/(*p);//*p指的是被除数


4.整形常量


我们知道一个整形常量第一个字符是数字0,则这个常量代表的是八进制位数。


因此10与010的意思不一样。


而有的C编译器会把8,9也作为八进制数字来处理,感觉很奇怪,所以ANSI C标准禁止了这种用法,以免混乱。


不过有时候,在上下文中为了格式对齐,可能会将十进制数写成八进制数。比如:


035
044
123//只是单词的使格式对齐没有多的意思,


5.字符和字符串


C语言中字符与字符串的区别在于单引号与双引号的使用,在某些情况下如果把两者弄混,编译器并不会监测报错,从而运行时产生难以预料的结果。我们先来看看字符与字符串的区别

5.1 字符


用单引号引起的一个字符实际上代表一个整数,整数对应着ASCII码值的字符序列值,比如’a‘的含义与0141(八进制)或者97(十进制)严格一致。


整数(一般为16位或32位)的存储空间可以容纳多个字符(一般位8位)因此有的C编译器允许在一个字符常量(以及字符串常量)中包含多个字符。也就是用‘yes’代替“yes”不会被该编译器监测到。“yes”代表的意思是依次包含‘y’,‘e’,‘s’以及空字符‘\0’的4个练习内存单元的首地址。而‘yes’的含义并没有准确的进行定义,但大多数编译器理解为“一个整数值”


257e9f22b0ef490a9e9924e770688d53.png


5.2 字符串


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


#include <stdio.h>
int main()
{
  printf("xiao tao lai lo\n");
  char ch[100] = "xiao tao lai lo";
  //ch是数组名,数组名是首元素的地址,也就是首字符的地址。
  printf(ch);//ch里存放着首元素的地址
}

a500cd741a284cdea6c57fdbcaac73a7.png


这说明这两个打印效果是一样的,所以一个字符串代表着一个指向无名数组起始字符的指针。可以用一个指针来存放字符串


#include <stdio.h>
int main()
{
char* p = "xiao tao lai lo";
  //指针p里面存放的就是字符串的首字符地址。
  printf(p);
}

eae7bddb89eb4a8ca0363dd142d772dc.png


6.练习题


6.1 练习题①


c3a60356f40b41d68ce2d46ef73ac43e.png


答案:

根据“贪心法”规则,在编译器读入>之前,就已经将- -作为一个符号,而后面的>不能和前面的组成一个符号。


6.2 练习题②


9e11aed8f9d04479aaa531bae445b083.png


答案:

这个代码唯一有意义的解析是这样:


a++ + ++b


但是根据“贪心法”规则,上面代码应该被分解为

a++ ++ +b


这个式子从语法上来说是不对的,它应该写成


((a++)++)+b


但是,a++的结果不能作为左值,因此编译器不会接收a++作为后面的++运算符的操作数,该代码没有意义。


……………………………………………………………………06fee652630344ba9668d8a5463fbc35.gif

………………………………………………………………………………………………………………………加油…………………………………………………………

相关文章
|
供应链 安全 搜索推荐
大模型加速零售业重构
【1月更文挑战第19天】大模型加速零售业重构
413 2
大模型加速零售业重构
|
存储 Kubernetes IDE
|
机器学习/深度学习 数据采集 数据可视化
R语言 一种功能强大的数据分析、统计建模 可视化 免费、开源且跨平台 的编程语言
R语言 一种功能强大的数据分析、统计建模 可视化 免费、开源且跨平台 的编程语言
913 1
|
8月前
|
数据采集 存储 机器学习/深度学习
构建高效的LinkedIn图像爬取工具
构建高效的LinkedIn图像爬取工具
|
9月前
|
人工智能 Java 程序员
一文彻底搞定电阻元件
电阻元件是限流器件,通过其电流与两端电压成正比(V=IR),阻值受温度、材料等影响。按特性分为线性与非线性,材料上有碳膜、金属膜等,用途涵盖限流、分压、偏置、滤波等。标称阻值有允许偏差,额定功率和最高工作电压需注意。色标法和直接读取法可用于识别阻值,万用表测量时需关闭电源并选择合适量程。电阻在电路设计中不可或缺,掌握其特性和应用对电子工程师至关重要。
689 0
一文彻底搞定电阻元件
|
人工智能 Serverless API
不用看就能懂,快速理解海量视频内容
想要快速理解海量视频内容?借助视频视觉理解技术,您可以轻松实现视频内容的自动化分析和信息提取。这项AI技术不仅大幅提升了工作效率,还为开发者提供了更多创意和应用的可能性。
|
前端开发 rax Shell
[PWN][高级篇]ROP-ret2libc-32/64位实例 (共四个)(上)
[PWN][高级篇]ROP-ret2libc-32/64位实例 (共四个)
1129 0
[PWN][高级篇]ROP-ret2libc-32/64位实例 (共四个)(上)
|
存储 文件存储 数据安全/隐私保护
exFAT和NTFS的区别是什么
exFAT和NTFS的区别是什么
2865 9
|
存储 关系型数据库 MySQL
服务器数据恢复—EVA存储异常断电重启后虚拟机无法启动的数据恢复方案
服务器存储数据恢复环境: 某品牌EVA8400,服务器上安装VMware ESXi虚拟化平台,虚拟机的虚拟磁盘包括数据盘(精简模式)+快照数据盘,部分虚拟机中运行oracle数据库和mysql数据库。 服务器存储故障&检测: 存储异常断电重启后,存储中一台虚拟机无法启动。工作人员推测故障原因是异常断电导致电源模块出现故障,清空cache后重新启动存储发现该虚拟机仍无法正常启动。
|
域名解析 存储 网络安全
WordPress外贸建站教程
这篇WordPress外贸建站教程是以实操形式写给没有任何建站基础的新手,不管你是不是技术小白,都可以轻松学会如何使用WordPress来自己建立一个实用的外贸网站,而不需要深入了解复杂的代码编程。梳理了WordPress外贸建站主要步骤,从最初的成本分析开始,然后逐步介绍域名选择和注册、虚拟主机选择、建站程序安装等关键步骤。
759 3