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

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++在这方面引入了一些新的方法,使得预处理器几乎无用武之地。

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),比赛要求参赛的程序员尽可能地编写神秘而混乱的程序来压倒对手(关于这个比赛,以后还有更详尽的说明)。

时间: 2024-11-13 09:34:08

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

《C专家编程》一导读

前 言 C专家编程 C代码.C代码运行.运行码运行-请! --Barbara Ling 所有的C程序都做同一件事,观察一个字符,然后啥也不干. --Peter Weinberger 你是否注意到市面上存有大量的C语言编程书籍,它们的书名具有一定的启示性,如:C Traps and Pitfalls(本书中文版<C陷阱与缺陷>已由人民邮电出版社出版), The C Puzzle Book, Obfuscated C and Other Mysteries,而其他的编程语言好像没有这类书.这里有一

如果编程界推行中文标准的话

    何曾几时CCTV5的NBA不叫NBA而叫美国职业男篮,后来又简短为美职篮,那WNBA叫啥呢?美女职篮吗?     如果在编程界推行中文标准:      为了保证完全的汉语纯洁性,从今天起我们使用完全中文编程(比如易语言),符号和空格必须保持全角,程序员需要在程序左下角加盖个人签章. 在谈到以下词汇时请使用纯洁汉语名称:  Linus Torvalds --林纳斯·脱袜子 Git--由林纳斯·脱袜子开发的一种分布式版本控制系统 VI 编辑器--看你看你一眼编辑器 Go语言--走你语言 Py

《C专家编程》一1.11 轻松一下——由编译器定义的Pragmas效果

1.11 轻松一下--由编译器定义的Pragmas效果 自由软件基金会(Free Software Foundation)是一个独特的组织,它由MIT顶级黑客Richard Stallman所创立.顺便提一下,我们所说的"黑客",它的原先意思是"天才程序员".后来这个称呼被媒体所贬损,致使它在局外人眼中成了"邪恶的天才"的代名词.和形容词"bad"一样,"黑客"现在也有两个相反的意思,必须通过上下文才能明白

UNIX环境高级编程---标准I/O库

前言:我想大家学习C语言接触过的第一个函数应该是printf,但是我们真正理解它了吗?最近看Linux以及网络编程这块,我觉得I/O这块很难理解.以前从来没认识到Unix I/O和C标准库I/O函数压根不是一码事.Unix I/O也叫低级I/O,也叫Unbuffered I/O,是操作系统内核部分,也是系统调用:而C标准I/O函数相对也成Buffered I/O,高级I/O,一般是为了效率考虑对这些系统调用的封装.以前使用getchar()经常为输入完后的回车而出错.那是不理解标准I/O实现时的

【Linux系统编程】 浅谈标准I/O缓冲区

标准I/O库提供缓冲的目的是尽可能地减少使用read和write调用的次数.它也对每个I/O流自动地进行缓冲管理,从而避免了应用程序需要考虑这一点所带来的麻烦.不幸的是,标准I/O库最令人迷惑的也是它的缓冲. 标准I/O提供了三种类型的缓冲: 1.全缓冲: 在填满标准I/O缓冲区后才进行实际I/O操作.常规文件(如普通文本文件)通常是全缓冲的. 2.行缓冲: 当在输入和输出中遇到换行符时,标准I/O库执行I/O操作.这允许我们一次输出一个字符,但只有在写了一行之后才进行实际I/O操作.标准输入和

详解C语言编程中预处理器的用法_C 语言

预处理最大的标志便是大写,虽然这不是标准,但请你在使用的时候大写,为了自己,也为了后人. 预处理器在一般看来,用得最多的还是宏,这里总结一下预处理器的用法. #include <stdio.h> #define MACRO_OF_MINE #ifdef MACRO_OF_MINE #else #endif 上述五个预处理是最常看见的,第一个代表着包含一个头文件,可以理解为没有它很多功能都无法使用,例如C语言并没有把输入输入纳入标准当中,而是使用库函数来提供,所以只有包含了stdio.h这个头文

《APUE》读书笔记—第五章标准I/O库

标准I/O库是ISO C的标准,在很多操作系统上面都实现.Unix文件I/O函数都是针对文件描述符的,当打开一个文件的时候,返回该文件描述符用于后续的I/O操作.而对于标准I/O库,操作则是围绕流进行,当用标准I/O库打开或者创建一个文件时,使得一个流与文件相关联.标准I/O库使用了缓冲技术,使用缓冲的目的是尽可能减少使用read和write调用次数,但是效率不高.每次进行读写时候需要复制两次数据.第一次是在内核和标准I/O缓冲之间(调用read和write),第二次是在标准I/O缓冲区和用户程

《嵌入式C编程:PIC单片机和C编程技术与应用》一1.4 C预编译指令

本节书摘来自华章出版社<嵌入式C编程:PIC单片机和C编程技术与应用>一书中的第1章,第1.4节,作者 [美]马克·西格斯蒙德(Mark Siegesmund),更多章节内容可以访问"华章计算机"公众号查看 1.4 C预编译指令 预编译是C语言中一个非常有意思的特性.预处理使用工具(预处理器)在编译前先扫描一遍代码,并对代码做出相应的修改从而生成用来编译的代码.预编译指令由#开始,占用一整行.在第3章中将会详细介绍它.在上面的例子中,#include指令将文件(e3.h)的

《C专家编程》一1.6 它很棒,但它符合标准吗

1.6 它很棒,但它符合标准吗 不要添乱--立即解散ISO工作小组. --匿名人士 ANSI C标准可以说是非常独特的,我们可以从好几个有趣的方面来说明这一点.它定义了下面一些术语,用于描述某种编译器的特点.如果你对这些术语有一个比较好的了解,就有助于你理解什么东西能被语言接受,什么东西不能被语言接受.前两个术语涉及不可移植的代码(unportable code),接下来的两个术语跟坏代码(bad code)有关,而最后两个术语则跟可移植的代码(portable code)有关. 不可移植的代码