[有人问了我一个问题,这个问题现在在 comp.lang.c FAQ 列表的 17.4b 条目中,我的回复如下。]
发件人:scs@eskimo.com (Steve Summit)
主题:回复:一个“C”语言问题
日期:2000 年 6 月 1 日 星期四 11:43:30 -0700 (PDT)
消息 ID:<200006011843.LAA29337@mail.eskimo.com>
你写道
> 我有一个“C”语言编程问题。在 UNIX 系统中,/usr/include
目录下有一个 ctype.h 文件。
一般来说,在检查编译器提供的头文件(例如 /usr/include 中的文件)时,您需要小心。这些文件通常会利用“魔术”特性,这些特性不是标准的,有时也不是供用户使用的。不过,这个问题有一个相当直接的答案,而且它不依赖于任何特定的编译器。
> 在这个文件中,有一个原型定义
> extern int isalpha __((int)).
> 我不明白 <空格>__((int))。这是什么意思?
> 我猜它是 isalpha 函数的一个参数,但是
> 为什么中间会有一个空格呢?
这其实是一个技巧。
您可能知道,C 语言中有两种外部函数原型声明。老式声明如下:
extern int isalpha();并表示isalpha是一个返回int的函数。较新式的、带有原型(您可能只熟悉这种形式)的声明如下:
extern int isalpha(int);并提供了额外信息,即该isalpha函数接受一个类型为int.
的参数。有时,一段代码(在此例中是头文件)能够同时被 ANSI 兼容和预 ANSI 编译器编译,这是非常有用的。然而,函数原型是随着 ANSI 标准一起引入的。预 ANSI 编译器不理解函数原型,事实上,当预 ANSI 编译器看到函数原型时,通常会报告一个“语法错误”。
因此,这个技巧的第一部分是使函数原型定义的出现具有条件性。假设我们有宏定义
#define PROTOTYPE(args) args这看起来是一个相当无用的宏;它所做的只是接受一个宏参数并将其原样输出。但是,假设我们将 isalpha 的原型写成
extern int isalpha PROTOTYPE((int));经过预处理器展开宏后,我们会得到
extern int isalpha (int);这和我们原来的原型一样(除了插入了一个额外的空格,这并不重要)。
(这里还有一个额外的问题是,为什么我们必须像这样调用宏PROTOTYPE((int)),带有一对额外的括号。原因是,当我们使用PROTOTYPE宏来帮助我们声明一个接受多个参数的函数时,预处理器不会抱怨。如果我们调用PROTOTYPE(int, double),预处理器会抱怨我们用两个宏参数调用了PROTOTYPE宏,而它只期望一个。但是当我们像这样调用它时PROTOTYPE((int, double)),就预处理器而言,我们用一个参数调用了它,这个参数是“(int, double)”,包括一对括号。)
现在,我们有了一种方法来“关闭”原型。如果我们重新定义PROTOTYPE宏为
#define PROTOTYPE(args) ()那么,宏定义的 isalpha 声明将变成
extern int isalpha ();这将是预 ANSI 编译器可接受的。所以,把这些组合起来,我们可以说
#ifdef __STDC__ #define PROTOTYPE(args) args #else #define PROTOTYPE(args) () #endif extern int isalpha PROTOTYPE((int));
现在我们有了一段“向后兼容”于预 ANSI 编译器的代码。(显然,我们只需要做一次#ifdef/#define/#else事情,然后我们就可以在很多外部函数原型声明中使用PROTOTYPE宏了。)
唯一的问题是
extern int isalpha PROTOTYPE((int));看起来很丑,因为那个宏名PROTOTYPE在其中。有人认为值得尝试解决这个问题。
C 语言中标识符的规则是它们可以由字母、数字和下划线字符组成,并且第一个字符不能是数字。这意味着第一个字符可以是下划线。但这也意味着只由下划线组成的标识符是合法的!使用单个或双下划线作为标识符是一个相当狡猾的技巧,我们显然不能经常使用它,但是如果我们认为编写向后兼容的函数声明而不使用那个丑陋的PROTOTYPE名字如此重要,以至于我们愿意用这个狡猾的技巧来完成这项任务,我们可以将宏名“PROTOTYPE”替换为宏名“__”,从而得到
#ifdef __STDC__ #define __(args) args #else #define __(args) () #endif extern int isalpha __((int));
这样就解释了您看到的内容。(我个人认为这一切都不值得,我自己也不使用这个技巧,但我已经在其他人编写的许多代码中看到过它。)
Steve Summit