当前位置:网站首页>C专家编程 第2章 这不是Bug,而是语言特性 2.2 多做之过
C专家编程 第2章 这不是Bug,而是语言特性 2.2 多做之过
2022-08-03 16:09:00 【weixin_客子光阴】
/*多做之过就是指语言中存在某些不应该存在的特性
*例如容易出错的switch语句,相邻字符串常量的自动连接,缺省全局作用域
*/
/*由于存在fall through, switch语句会带来麻烦
//1.
switch(表达式) {
case 常量表达式:零条或多条语句
default: 零条或多条语句
case 常量表达式:零条或多条语句
}
/*每个case结构由3个部分组成:关键字case;紧随其后的常量值或常量表达式;在紧接
*一个冒号。当表达式的值与case中的常量匹配时,该case后面的语句就会执行。default(如
*果有的话)可以出现在case列表的任何位置,它在其他的case均无法匹配时被选中执行。如
*过没有default,而且所有的case均不匹配,则整条switch语句便什么也不做。
*/
/*在C语言中,几乎从来不进行运行时错误检查---对进行解除引用操作的指针进行有效
*性检查大概是唯一的例外,而且在MS-DOS系统里甚至连这点很有限的检查都无法保证。
*/
/*MS-DOS的运行时检查*/
/*在所有的虚拟内存体系结构中,一旦一个指针进行解除引用操作时所引用的内存地址
*超出了虚拟内存的地址空间,操作系统会中止这个进程。
*MS-DOS并不支持虚拟内存,即使内存访问失败,它也无法立即捕捉到这种情况
*MS-DOS可以在程序结束之后检测解除引用空指针的情况
*具体方法是在进入程序前,保存内存地址为零时存储的内容。在程序结束时,系统
*检查这个地址与原先的是否相同。如果不同,基本可以肯定你的程序使用了空指针来访问
*内存,运行时系统会打印一条“null pointer assignment"(空指针赋值)信息
*/
/*运行时检查与C语言的设计理念相违背。按照C语言的理念,程序员应该知道自己正在干什么,
*而且保证自己的所作所为是正确的
*/
/*各个case和default的顺序可以是任意的,但习惯上总是把default放在最后。
*一个遵循标准的C编译器至少允许一条switch语句中有257个case标签,
*这是为了允许switch满足一个8比特字符的所有情况(256个可能的值加上EOF)
*/
/*switch存在一些问题,其中之一就是它对case可能出现的值太过于放纵了。
*例如,可以在switch的左花括号之后可以声明一些变量,从而进行一些局部存储的分配
*在最初的编译器里,这是一个技巧---绝大多数用于处理任何复合语句的代码
*都可以被复用,可以用于处理switch语句中由花括号包住的那部分代码。
*所以在这个位置上声明一些变量会被编译器很自然地接受,尽管在switch语句中
*为这些变量加上初始值没有什么用处,因为它绝不会被执行---语句从匹配表达式
*的case开始执行
*/
//2.
/*小启发*/
/*需要一些临时变量吗?把它放在块的开始处*/
/*在C语言中,当建立一个块时,一般总是这样开始的:
*{
语句
*你总是可以在两者之间增加一些声明,如:
*{
声明
语句
*当分配动态内存代价较高时,你可能会采用这种局部存储的方法,但有可能的话要
*尽量避免。编译器可以自由地忽略它,它可以通过函数调用来分配所有局部块需要
*的内存空间。
*另一种用法是声明一些完全局部于当前块的变量
*/
/*声明一些完全局部于当前块的变量 */
if (a > b)
/*交换a, b*/
{
int temp = a;
a = b;
b = temp;
}
//C++在这方面又进了一步,允许语句和声明以任意的顺序交叉出现,甚至允许变量的声明出现在for表达式的内部
for (int i = 0; i < 100; i++) {
...
}
/*switch的另一个问题是它内部的任何语句都可以加上标签,并在执行时跳转到那里,这就有可能
*破坏程序流的结构化
*/
switch(i) {
case 5 + 3: do_again;
case 2: printf("I loop unremittingly\n"); goto do_again;
default: i++;
case 3: ;
}
//3.
/*顺便提一句,在C语言中,const关键字并不真正表示常量*/
const int two = 2;
switch(i) {
case 1: printf("case 1\n");
case two: printf("case 2\n");
//**error** ^^^integral constant expression expected
case 3: printf("case 3\n");
default: ;
}
/*switch语句最大的缺点是它不会在每个case标签后面的语句执行完毕后自动中止。
*一旦执行某个case语句,程序将会执行后面所有的case,除非遇到break语句
*"fall through"意思是:如果case语句后面不加break,就依次执行下去,以满足某些
*特殊情况的要求。
*实际上,这是一个非常不好的特性,因为几乎所有的case都需要以break结尾。
*大部分lint程序在发现“fall through”情况时会发出警告信息。
*/
switch(2) {
case 1: printf("case 1\n");
case 2: printf("case 2\n");
case 3: printf("case 3\n");
case 4: printf("case 4\n");
default: printf("default\n");
}
/*软件信条*/
/*缺省采用“fall through”,在97%的情况下都是错误的*/
/*在编译可能具有一个或两个操作数的操作符时:*/
switch(operator->num_of_operands) {
case 2: process_operand(operator->operand_2);
/*fall through*/
case 1: process_operand(operator0->operand_1);
break;
}
//4.
/*switch的另一个问题---break中断了什么*/
/*它证明了在C语言中,人们太容易低估break语句对控制结构的影响*/
network code() {
switch(line) {
case THING1:
doit1();
break;
case THING2:
if (x == STUFF) {
do_first_stuff();
if (y == OTHER_STUFF) {
break;
}
do_latter_stuff();
} /*代码的意图是跳到这里...*/
initialize_modes_pointer();
break;
default:
processing();
} /*...但事实上跳到了这里*/
use_modes_pointer(); /*致使modes_pointer未初始化*/
}
/*break语句事实上跳出最近的那层循环或switch语句*/
//5.
/*粉笔也成了可用的硬件*/
/*ANSI引入的另一个新特性是“相邻的字符串常量将被自动合并成一个字符串。
*这就省掉了过去在书写多行信息时必须在行末加“\”的做法,后续的字符串可以
*出现在每行的开头
*/
/*旧风格*/
printf("A favorite children's book \
is 'muffy Gets It: the hilarious table of a cat, \
a boy, and his machine gun");
/*现在可以用一连串相邻的字符串常量来代替它,它们会在编译时自动合并。除了最后
*一个字符串外,其余每个字符串末尾的'\0'字符会被自动删除
*/
/*新风格*/
printf("A favorite children's book "
"is 'muffy Gets It: the hilarious table of a cat, "
"a boy, and his machine gun");
/*Crayon 粉笔
*automated generation 自动生成
*/
/*然而,这种合并意味着字符串在初始化时,如果不小心漏掉了一个逗号,编译器
*将不会发出错误信息,而是悄无声息地把两个字符串合并在一起。
*/
char *available_resources[] = {
"color monitor",
"big disk",
"Cray" /*哇!少了个逗号*/
"on-line drawing routhiness",
"mouse",
"keyboard",
"power cables", /*这个多余的逗号会引起什么问题吗?*/
};
/*顺便提一句,最后那个字符末尾的逗号并不是打字错误,而是从最早的C语法中继承下来的东西
*,不管存在与否都没有什么意义。ANSI C Rationale对它进行了辩护,称它使C语言在自动生成
*(automated generation)时更容易一些。我想,这种拖尾巴如果在其他由逗号分割的列表(如
*枚举声明、单行多变量声明等)中也允许使用,那还说得过去,可惜事实并非如此
*/
//6.
/*小启发*/
/*使一段代码在第一次执行时的行为与以后执行时不同
*这种方法能使分支和条件测试减少到最小程度
*/
void generate_initializer(char *string)
{
static char separator = ' ';
printf("%c %s\n", separator, string);
separator = ',';
}
//7.
/*太多的缺省可见性*/
/*定义C函数是,在缺省情况下函数的名字全局可见的,跟加一个冗余的
*extern关键字效果是一样的。这个函数对于链接到它所在的目标文件的任何东西都是可见的。
*如果想限制对这个函数的访问,就必须加static关键字
*/
/*interpositioning就是用户编写和库函数同名的函数并取而代之的行为*/
function apple(); {/*在任何地方均可见*/}
extern function pear(); {/*在任何地方均可见*/}
static function trunip() {
/*在这个文件之外不可见*/
}
/*根据实际经验,这种缺省的全局可见性多次证明是个错误,这已是盖棺定论。软件对象在
*大多数情况下应该缺省地采用有限可见性。当程序员需要让它全局可见时,应该采用显式的手段
*/
/*这种太大范围的全局可见性会与C语言的另一个特性相互产生影响,那就是interpositioning。
*interpositioning就是用户编写和库函数同名的函数并取而代之的行为。
*/
/*作用域过宽的问题常见于库中:一个库需要让一个对象在另一个库中可见。
*唯一的办法是让它变得全局可见。但这样一来,它对于链接到该库的所有对象
*都是可见的了。这就是all_or_nothing---一个符号要么全局可见,要么对其他
*文件都不可见
*Pascal中那样在一个函数内部嵌套另一个函数的定义
*Ada和Modula-2就是在各个程序单元中明确说明那些符号是引入的,那些是引出的
*/
边栏推荐
- 使用Make/CMake编译ARM裸机程序(基于HT32F52352 Cortex-M0+)
- 使用VS Code搭建ESP-IDF环境
- Detailed ReentrantLock
- Analysis of ffplay video playback principle
- 面了个腾讯35k出来的,他让我见识到什么叫精通MySQL调优
- [QT] Qt project demo: data is displayed on the ui interface, double-click the mouse to display specific information in a pop-up window
- AI+BI+Visualization, Deep Analysis of Sugar BI Architecture
- 我在滴滴做开源
- MarkDown常用代码片段和工具
- 使用 PowerShell 将 Windows 转发事件导入 SQL Server
猜你喜欢
随机推荐
【无标题】
土耳其国防部:联合协调中心将对首艘乌克兰粮船进行安全检查
CopyOnWriteArrayList详解
使用 PowerShell 将 Windows 转发事件导入 SQL Server
CPU个数_核心数_线程数之间的关系
如何使用MATLAB绘制极坐标堆叠柱状图
Small Tools(4) 整合Seata1.5.2分布式事务
AI+BI+Visualization, Deep Analysis of Sugar BI Architecture
如何启动 NFT 集合
攻防世界----bug
Tolstoy: There are only two misfortunes in life
window.open does not show favicon.icon
2年开发经验去面试,吊打面试官,即将面试的程序员这些笔记建议复习
技术干货|如何将 Pulsar 数据快速且无缝接入 Apache Doris
WordPress 5.2.3 更新,升级出现请求超时的解决方法
devops-2:Jenkins的使用及Pipeline语法讲解
SQL中对 datetime 类型操作
C语言02、语句、函数
Hannah荣获第六季完美童模全球总决赛全球人气总冠军
机器人开发--Universal Scene Description(USD)