《C专家编程》笔记之 C 语言的失误

第一章讲的是历史,就不做笔记了,直接从第二章开始。

C 的失误:switch case 语句默认 fall through

也许 switch 语句最大的缺点是它不会在每个 case 标签后面的语句执行完毕后自动中止。一旦执行某个case语句,程序将会依次执行后面所有的case,除非遇到 break 语句。执行下述代码:

switch(2) {
  case 1: printf("1\n");
  case 2: printf("2\n");
  case 3: printf("3\n");
  case 4: printf("4\n");
  default: printf("default \n");
}
复制代码

输出结果是一次打印 2、3、4 和 default。这种行为叫做「fall through」。

switch语句的默认行为在 97% 的情况下都是错误的,我们认为这是 C 语言的设计失误。

Golang 解决了这个问题,在 Go 里,你想要 fallthrough 需要手动添加对应的关键字:

i := 45
switch {
case i < 10:
    fmt.Println("i is less than 10")
    fallthrough
case i < 50:
    fmt.Println("i is less than 50")
    fallthrough
case i < 100:
    fmt.Println("i is less than 100")
}
// i is less than 50
// i is less than 100
复制代码

C 的失误:顶级函数全局可见

function apple() {  /* 在任何地方均可见 */ }
extern function pear()  {  /* 在任何地方均可见 */ }
static function turnip() {  /* 只在当前这个文件可见 */}
复制代码

这种太大范围的全局可见性会与C语言的另一个特性相互产生影响,那就是interpositioning。interpositioning就是用户编写和库函数同名的函数并取而代之的行为。许多C程序员完全没有注意过这个特性。

作用域过宽的问题常见于库中:一个库需要让一个对象在另一个库中可见。唯一的方法是让它变得全局可见。但这样一来,它对于链接到该库的所有对象都是可见的了。这就是all-or-nothing—— 一个符号要么全局可见,要么对其他文件都不可见。在C语言中,对信息可见性的选择就是这么有限。

由于 C 语言不支持在一个函数内部定义另一个函数,所以很多函数就会被写在顶级作用域;再加上很多程序员没有给函数加 static 的习惯,所以命名冲突的情况变得更加严重。

Golang 解决了这个问题,一个包里的函数默认只在当前包可见,除非你将它的第一个字母大写,大写之后,就在包外可见(但需要 import)。

C 的失误:lint 程序被分离出来

在UNIX早期的C语言中,语言设计者做出了一个决定,把编译器中所有的语义检查措施都分离出来。错误检查由一个单独的程序完成,这个程序被称为lint。在省掉了全面的错误检查后,编译器可以做得更小、更快而且更简单。这种做法是错的!

把lint程序从编译器中分离出来作为一个独立的程序是一个严重的失误,人们直到现在才意识到这个问题。确实,把lint程序分离出来以后,编译器变得更小,目标也更为专一。但是,它所付出的巨大代价是,代码中悄悄混进了大量的Bug和不可靠的编码风格。许多(也许是绝大多数)程序员默认情况下在每次编译时并不使用lint程序。让充满Bug的代码快速通过编译实在是很不划算。现在,许多lint程序中的检查措施又重新出现在编译器中。

软件的经济规律显示,越是在开发周期的早期发现Bug,修复它所付出的代价就越小。所以让lint程序(如果是编译器就更好了)取代调试器来执行寻找Bug的额外工作是一笔很合算的投资。

Golang 已经将 lint、formatter 和编译器强制捆绑在一起了。

以上是本书第二章的主要内容,Golang 相关的内容我自己加上去的。

猜你喜欢

转载自juejin.im/post/7104850705290100743
今日推荐