《C专家编程》一1.3 标准I/O库和C预处理器

简介:

本节书摘来自异步社区《C专家编程》一书中的第1章,第1.3节,作者 【美】Perter Van Der Linde,更多章节内容可以访问云栖社区“异步社区”公众号查看

1.3 标准I/O库和C预处理器

C编译器不曾实现的一些功能必须通过其他途径实现。在C语言中,它们在运行时进行处理,既可以出现在应用程序代码中,也可以出现在运行时函数库(runtime library)中。在许多其他语言中,编译器会植入一些代码,隐式地调用运行时支持工具,这样程序员就无须操心它们了。但在C语言中,绝大多数库函数或辅助程序都需要显式调用。例如,在C语言中(必要时),程序员必须管理动态内存的使用,创建各种大小的数组,测试数组边界,并自己进行范围检测。

与此类似,C语言原先并没有定义I/O,而是由库函数提供。后来,这实际上成了标准机制。可移植的I/O由Mike Lesk编写,最初出现在1972年左右,可在当时存在的3个平台上通用。实践经验表明,它的性能低于预期值。所以,人们对它又进行了优化和裁剪,后来成为标准I/O函数库。

C预处理器大约也是在这个时候被加入的,倡议者是Alan Snyder。它所实现的3个主要功能是:

字符串替换:形式类似“把所有的foo替换为baz”,通常用于为常量提供一个符号名。

头文件包含(这是在BCPL中首创的):一般性的声明可以被分离到头文件中,并且可以被许多源文件使用。虽然约定采用“.h”作为头文件的扩展名,但在头文件和包含实现代码的对象库之间在命名上却没有相应的约定,这多少令人不快。

通用代码模板的扩展。与函数不同,宏(marco)在连续几个调用中所接收的参数的类型可以不同(宏的实际参数只是按照原样输出)。这个特性的加入比前两个稍晚,而且多少显得有些笨拙。在宏的扩展中,空格会对扩展的结果造成很大的影响。

#define a(y)  a_expanded(y)
a(x);

被扩展为:

a_expanded(x);

而:

#define a (y)   a_expanded (y)
a(x);

则被扩展为:

(y)    a_expanded (y)(x)

它们所表示的意思风马牛不相及。你可能会以为在宏里面使用花括号就像在C语言的其他部分一样,能把多条语句组合成一条复合语句,但实际上并非如此。

这里对C语言的预处理器并不作太多的讨论。这反映了这样一个观点:对于宏这样的预处理器,只应该适量使用,所以无须深入讨论。C++在这方面引入了一些新的方法,使得预处理器几乎无用武之地。


8c41a84534cfaa7e8e47cad8f509dac31bb9999a

C并非Algol

70年代后期,Steve Bourne在贝尔实验室编写UNIX第7版的shell(命令解释器)时,决定采用C预处理器使C语言看上去更像Algol-68。早年在英国剑桥大学时,Steve曾编写过一个Algol-68编译器。他发现如果代码中有显式的“结束语句”提示,诸如if ... fi或者case ... esac等,调试起来会更容易。Steve认为仅仅一个“}”是不够的,因此他建立了许多预处理定义:

#define STRING char *
#define IF if(
#define THEN ){
#define ELSE }else(
#define FI ;}
#define WHILE while(
#define DO ){
#define OD ;}
#define INT int
#define BEGIN {
#define END }

这样,他就可以像下面这样编写代码:

INT compare(s1, s2)
    STRING s1;
    STRING s2;
BEGIN
    WHILE *s1++ == *s2
    DO IF *s2++ == 0
        THEN return(0);
        FI
    OD
       return(*--s1 - *s2);
END

再看一下相应的C代码:

int compare(s1, s2)
    char *s1, *s2;
{
    while(*s1++ == *s2){
            if(*s2++ == 0) return(0);
    }
    return (*--s1 - *s2);
}

Bourne shell的影响远远超出了贝尔实验室的范围,这也使得这种类似Algol-68的C语言变型名声大噪。但是,有些C程序员对此感到不满。他们抱怨这种记法使别人难以维护代码。时至今日,BSD 4.3 Bourne shell(保存于/bin/sh)依然是这种记法写的。

我有一个特别的理由反对Bourne Shell,在我的书桌上堆满了针对它的Bug报告!我把它们发给Sam,我们都发现了这样的Bug:这个shell不使用malloc,而是使用sbrk自行负责堆存储的管理。在维护这类软件时,每解决两个问题通常又会引入一个新问题。Steve解释说他之所以采用这种特制的内存分配器,是为了提高字符串处理的效率,他从来不曾想到其他人会阅读他的代码。
Bourne创立的这种C语言变型事实上促成了异想天开的国际C语言混乱代码大赛(The International Obfuscated C Code Competition),比赛要求参赛的程序员尽可能地编写神秘而混乱的程序来压倒对手(关于这个比赛,以后还有更详尽的说明)。

相关文章
|
17天前
|
编译器 C++
C++语言预处理器学习应用案例
【4月更文挑战第8天】C++预处理器包括条件编译、宏定义和文件包含等功能。例如,条件编译用于根据平台选择不同代码实现,宏定义可简化常量和变量名,文件包含则用于整合多个源文件。示例中展示了如何使用`#ifdef`等指令进行条件编译,当`DEBUG`宏定义时,`PRINT_LOG`会打印调试信息,否则不执行。
13 1
|
1月前
|
存储 XML JSON
【软件设计师备考 专题 】深入理解代码标准和文件格式标准
【软件设计师备考 专题 】深入理解代码标准和文件格式标准
47 0
|
1月前
|
程序员 API C语言
在C++语言的标准I/O库
在C++语言的标准I/O库
10 0
|
2天前
|
JSON 人工智能 数据库
【AI大模型应用开发】【LangChain系列】1. 全面学习LangChain输入输出I/O模块:理论介绍+实战示例+细节注释
【AI大模型应用开发】【LangChain系列】1. 全面学习LangChain输入输出I/O模块:理论介绍+实战示例+细节注释
25 0
【AI大模型应用开发】【LangChain系列】1. 全面学习LangChain输入输出I/O模块:理论介绍+实战示例+细节注释
|
1月前
|
存储 编解码 网络协议
音视频编程ffmepg中的关键术语与概念:深度解析与实践(一)
音视频编程ffmepg中的关键术语与概念:深度解析与实践
72 0
|
1月前
|
存储 编解码 算法
音视频编程ffmepg中的关键术语与概念:深度解析与实践(二)
音视频编程ffmepg中的关键术语与概念:深度解析与实践
60 0
|
3月前
【突破常规:让函数规范成为注目的亮点】(下)
【突破常规:让函数规范成为注目的亮点】
|
3月前
【突破常规:让函数规范成为注目的亮点】(上)
【突破常规:让函数规范成为注目的亮点】
|
9月前
|
算法 Java 程序员
01-C++核心语法|C++概述【C++简介、C++起源、可移植性和标准、为什么C++会成功、从一个简单的程序开始认识C++】
复习`C++核心语法`,且适当进行汇编探索底层实现原理,进一步夯实基础,为以后的`底层开发`、`音视频开发`、`跨平台开发`、`算法`等方向的进一步学习埋下伏笔。
01-C++核心语法|C++概述【C++简介、C++起源、可移植性和标准、为什么C++会成功、从一个简单的程序开始认识C++】
|
10月前
|
机器学习/深度学习 算法 语音技术
llama.cpp作者创业,用纯C语言框架降低大模型运行成本
llama.cpp作者创业,用纯C语言框架降低大模型运行成本
683 0