C语言笔记之头文件与链接(二)

从上篇文章中,我们可以看到一点头文件的作用:就是声明各个函数或变量,以供调用;而至于函数或变量的本体,在链接阶段补上。在main.c中。我们手动声明了两个函数,但其实这样比较费力不讨好,因为如果还有很多其他文件也需要调用这两个函数,那么也要在那些文件中一次次的声明;两个函数还好,如果是成千上百个呢?还要一个一个的去声明吗?这时候,头文件就是一个更好的选择:只要把那些需要用到的函数或变量写进头文件,然后include这个头文件就可以了。头文件就是声明的替代,或者说是批量的声明。

我们的头文件file1.h可以这样写:

#include <stdio.h>

#include <stdlib.h>

#include <math.h>

extern int gen_rnd(void);

extern void judge(int, int);

以前我在写代码的时候,经常会有这样的疑问:我能不能调用另一个文件中的函数?或者我该如何调用另一个文件中的函数?是不是只要“另一个文件”在同一目录下,我就可以随意调用它的代码?不是有所谓的外链吗?

现在终于有了答案:如果想要调用另一个文件的函数或变量,首先被调用的函数或变量必须先声明为具有外链的(详见我介绍存储类的那篇文章);然后在本文件中声明相应的函数或变量(包括引用相关头文件);最后,要链接相应的目标文件。这样,我们就完成了所谓的跨文件调用。

我在介绍存储类的那篇文章中提到过,函数声明只要不被static声明,都是具有外链的(extern可以被省略);而想要变量具有外链,必须将其声明在所有函数之外(即使其具有文件作用域),同时用关键字extern修饰。这样以来,这些函数或变量除了在定义所在的文件中有声明外(定义本身即是一种声明),在所有引用它们的文件中也会有一份对应的声明,这些“备份”声明的作用就是告诉引用它们的文件:被声明的函数或变量,是同一个函数或变量,在完成链接之后指向相同的地址。因而,在我们的例子中,main.c声明了judge()函数,就不能再定义一个同名的函数了,除非决定不再链接judge.o。

下面在谈一谈如何寻找头文件以及如何根据头文件(或声明)在链接阶段寻找对应的目标文件。#include命令后面的头文件其实分为两类,一种是用尖括号扩起来的,而另一种是用双引号扩起来的。在我们的例子中,假设头文件file1.h放在main.c所在的目录,那么编译main.c的命令是这样的:

gcc -s main.c

gcc会自动在main.c所在的目录搜索头文件。如果file1.h放在main.c所在的目录的一个子目录sub中,那么编译命令是:

gcc -s main.c -I sub

-I 选项用来指定头文件的搜索路径,这个路径是被编译文件的相对路径,起始目录就是被编译文件所在目录。当然,这个相对路径也可以体现在#include命令中,比如,#include "sub/file1.h"  那么编译的时候就不需要-I选项了。

对于用角括号包含的头文件, gcc 首先查找 -I 选项指定的目录,然后查找系统的头文件目录(通常是 /usr/include );而对于用引号包含的头文件, gcc 首先查找包含头文件的 .c 文件所在的目录,然后查找 -I 选项指定的目录,然后查找系统的头文件目录。

指定头文件的路径是为了在编译的时候生成正确的声明,声明有了,如何去找到对应的目标文件呢?对于自己写的头文件,当然是自己在链接的时候给出路径来;而对于用尖括号括起来的头文件呢?gcc会自动去寻找相应的库文件,比如printf函数就在libc库中。这些标准库文件的路径都是系统固定的,gcc会默认去这些目录搜索,编译器默认会找的目录可以用 -print-search-dirs 选项查看。

那么库文件(暂时只考虑静态库)和目标文件有啥区别?其实我们也可以把刚才的judge.o和gen_rnd.o做成一个静态库:

$ ar rs libgame.a judge.o gen_rnd.o

库文件名都是以 lib 开头的,静态库以 .a 作为后缀,表示Archive。 ar 命令类似于 tar 命令,起一个打包的作用,但是把目标文件打包成静态库只能用 ar 命令而不能用 tar 命令。选项 r 表示将后面的文件列表添加到文件包,如果文件包不存在就创建它,如果文件包中已有同名文件就替换成新的。 s 是专用于生成静态库的,表示为静态库创建索引,这个索引被链接器使用。

然后我们可以这样编译main.c:

gcc main.c -L . -I game -o main

-L 选项告诉编译器去哪里找需要的库文件,-L. 表示在当前目录找, -l (小写的L)选项告诉编译器要链接libgame库.注意,即使库文件就在当前目录,编译器默认也不会去找的,所以 -L选项不能少。

那为什么要把目标文件做成库文件呢?首先,如果有太多目标文件的话,gcc命令会敲的手疼(⊙﹏⊙b汗),而库文件的编译命令就很简洁;其次,假设我们又在judge.c中添加了一个无关的add函数,那么直接链接目标文件,会把这些无关代码也加进可执行文件中,于是如果无关函数很多的话,就是使得可执行文件变得很大,但是如果链接库文件的话,链接器可以从静态库中只取出需要的部分来做。

最后留个尾巴:为什么不直接include “judge.c”等那些源文件呢?

时间: 2024-11-08 19:07:59

C语言笔记之头文件与链接(二)的相关文章

C语言笔记之头文件与链接(一)

<span style="font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);"><span style="font-size:18px;">虽然一直在用#include命令包含头文件,但其实一致不太明白头文件的原理.今天就研究了一下.</span></span> 首先,在大型项目中,仅仅一个源文件是不够的,巨大

C语言怎么写头文件?

C语言中.h和.c文件解析(很精彩)   简单的说其实要理解C文件与头文件(即.h)有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程: 1.预处理阶段 2.词法与语法分析阶段 3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件 (.obj文件) 4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制码,也就是去掉了文件格式信息.(生成.exe文件)

为什么C语言会有头文件

前段时间一个刚转到C语言的同事问我,为什么C会多一个头文件,而不是像Java和Python那样所有的代码都在源文件中.我当时回答的是C是静态语言很多东西都是需要事先定义的,所以按照惯例我们是将所有的定义都放在头文件中的.事后我再仔细想想,这个答案并不不能很好的说明这个问题.所以我在这将关于这个问题的相关内容写下来,希望给大家一点提示,也算是一个总结 include语句的本质 要回答这个问题,首先需要知道C语言代码组织问题,也就是我比较喜欢说的多文件,这个不光C语言有,几乎所有的编程语言都有,比如

Golang使用pkg-config自动获取头文件和链接库的方法

为了能够重用已有的C语言库,我们在使用Golang开发项目或系统的时候难免会遇到Go和C语言混合编程,这时很多人都会选择使用cgo. 话说cgo这个东西可算得上是让人又爱又恨,好处在于它可以让你快速重用已有的C语言库,无需再用Golang重造一遍轮子,而坏处就在于它会在一定程度 上削弱你的系统性能.关于cgo的种种劣迹,Dave Cheney大神在他的博客上有一篇专门的文章<cgo is not Go>,感兴趣的同学可以看一看.但话说回来,有时候为了快速开发满足项目需求,使用cgo也实在是不得

C语言中的头文件

1.头文件#include <> :表示引用标准库头文件,编译器会从系统配置的库环境中去寻找 2.头文件#include "":一般表示用户自己定义使用的头文件,编译器默认会从当前文件夹中寻找,如果找不到,则到系统默认库环境中去寻找. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 在C语言家族程序中,头文件被大量使用.一般而言,每个C++/C程序通常由头文件(header files)和

转:C语言中的头文件可以自己写吗?

转自:http://www.eefocus.com/computer00/blog/08-09/155791_9ebdc.html 一些初学C语言的人,不知道头文件(*.h文件)原来还可以自己写的. 只知道调用系统库函数时,要使用#i nclude语句将某些头文件包含进去. 其实,头文件跟.C文件一样,是可以自己写的. 头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h保存就行了.头文件中一般放一些重复使用的代码,例如函数声明,变量声明,常数定义,宏的定义等等. <>是标准库的

C语言基础篇—头文件

一.简述 在C语言家族程序中,头文件被大量使用.一般而言,每个C++/C程序通常由头文件(header files)和定义文件(definition files)组成.头文件作为一种包含功能函数.数据接口声明的载体文件,主要用于保存程序的声明(declaration),而定义文件用于保存程序的实现 (implementation). 二.格式 头文件名:xxx.h(xxx为自定义的头文件名称). 文件内容格式: #ifndef _xxx_H_ #define _xxx_H_ 头文件内容 #end

C语言之在头文件中定义全局变量

通常情况下,都是在C文件中定义全局变量,在头文件中声明,但是,如果我们定义的全局变量需要被很多的C文件使用的话,那么将全局变量定义在头文件里面会方便很多,那到底是如何实现的? os_var.c文件内容 1 #define OS_GLOBALS 2 #include “os.h” os.h文件内容 1 #ifdef OS_GLOBALS 2 #define OS_EXT 3 #else 4 #define OS_EXT extern 5 #endif os.h中定义很多的全局变量,但是os.h又需

jni.h头文件详解(二)

一:struct JNINativeInterface_{} 结构体的作用:它有点像我们char字符驱动的 file_ops结构体,它定义各种函数对在(jni.h头文件详解一)中定义的各种数据的操作函数集体. 二:它包含那些针对Java中类和对象的相关操作呢如下图. 三:下面我们讲详细介绍14个部分方法的用法和解析 3.1.版本信息操作函数. 一.GetVersion jint (JNICALL *GetVersion)(JNIEnv *env) --模块信息:该模块主要针对的JNI接口的版本信