C 语言编写大型程序:问与答

问:这里没有任何例子是使用 #include 指令来包含源文件的。如果这样做了会发生什么?

答:这是合法的,但不是个好习惯。这里给出一个出问题的例子。假设 foo.c 中定义了一个在 bar.c 和 baz.c 中需要用到的函数 f,我们在 bar.c 和 baz.c 中都加上了如下指令:

1
#include "foo.c"

这些文件都会很好地被编译。但当链接器发现函数 f 的目标代码有两个副本时,问题就出现了。当然,如果只是 bar.c 包含此函数,而 baz.c 没有,那么将没有问题。为了避免出现问题,最好只用 #include 包含头文件而非源文件。

问:针对 #include 指令的精确搜索规则是什么?

答:这与所使用的编译器有关。C 标准在 #include 的表述中故意模糊不清。如果文件名用尖括号围起来,那么预处理器会到一些“由实现定义的地方”搜索。如果文件名用双引号围起来,那么就“以实现定义的方式搜索”文件,如果没有找到,再按前一种方式搜索。原因很简单:不是所有操作系统都有分层的(树形的)文件系统。

更加有趣的是,标准根本不要求尖括号内的名字是文件名,因此使用 <>#include 指令有可能完全在编译器内部处理。

问:我不理解为什么每个源文件都需要它自己的头文件。为什么没有一个大的头文件包含宏定义、类型定义和函数原型呢?通过包含这个文件,每个源文件都可以访问所需要的全部共享信息。

答:只用“一个大的头文件”确实可行,许多程序员使用这种方法。而且,这种方法有一个好处:因为只有一个头文件,所以要管理的文件较少。然而,对于大型程序来说,这种方法的坏处大于它的好处。

只使用一个头文件不能为以后阅读程序的人提供有用的信息。如果有多个头文件,读者可以迅速了解到特定的源文件需要使用程序的其他哪些部分。

此外,因为每个源文件都依赖于这个大的头文件,所以改变它会导致要对全部源文件重新编译,这是大型程序中的一个显著缺陷。更糟的是,因为包含了大量信息,所以头文件可能会频繁地改变。

问:这个部分的教程说到共享数组应该按照下列方式声明:

1
extern int a[];

既然数组和指针关系密切,那么用下列写法代替是否合法呢?

1
extern int *a;

答:不合法。在用于表达式时,数组“衰退”成指针。(当数组名用作函数调用中的实际参数时,我们已经注意到这种行为。)但在变量声明中,数组和指针是截然不同的两种类型。

问:如果源文件包含了不是真正需要的头,会有损害吗?

答:不会,除非头中的声明或定义与源文件中的冲突。否则,可能发生的最坏情况就是在编译源文件时时间会有少量增加。

问:我需要调用文件 foo.c 中的函数,所以包含了匹配的头文件 foo.h。程序可以通过编译,但是不能通过链接。为什么?

答:在 C 语言中编译和链接是完全独立的。头文件的存在是为了给编译器而不是给链接器提供信息。如果希望调用文件 foo.c 中的函数,那么需要确保对 foo.c 进行了编译,还要确保链接器知道必须在 foo.c 的目标文件中搜索该函数。通常情况下,这就意味着在程序的 makefile 或工程文件中命名 foo.c。

问:如果程序调用 <stdio.h> 中的函数,这是否意味着 <stdio.h> 中的所有函数都将和程序链接呢?

答:不是的。包含 <stdio.h>(或者任何其他头)对链接没有任何影响。在任何情况下,大多数链接器只会链接程序实际需要的函数。

问:从哪里可以得到 make 实用程序?

答:make 是标准的 UNIX 实用程序。GNU 的版本称为 GNU Make,包含在大多数 Linux 发行版中,也可以从自由软件基金会的网站上直接获取。

请参阅

(完)

comments powered by Disqus