C编程
在 C 语言中,库是一个包含头文件的集合,供其他程序使用。一个库因此由一个接口(在 .h
文件中定义,称为“头文件”)和一个实现(在 .c
文件中定义)组成。这个 .c
文件可能是预编译的,或者可能是程序员可以访问的。(注意:库可能会调用其他库中的函数,比如标准 C 库或数学库来完成各种任务。)
库的格式随操作系统和使用的编译器不同而有所变化。例如,在 Unix 和 Linux 操作系统中,库由一个或多个目标文件组成,这些目标文件通常是编译器(如果源代码是 C 或类似语言)或汇编器(如果源代码是汇编语言)的输出。这些目标文件然后通过 ar
程序打包成库,形成一个归档文件(归档文件是一个将多个文件存储为一个文件的程序,通常不涉及压缩)。库的文件名通常以 "lib" 开头并以 ".a" 结尾;例如,libc.a
文件包含标准 C 库,libm.a
包含数学函数,链接器会将其链接到最终程序中。其他操作系统如 Microsoft Windows 使用 .lib
扩展名表示库,.obj
扩展名表示目标文件。在 Unix 环境中,一些程序如 lex
和 yacc
会生成可以与 libl
和 liby
库链接的 C 代码来创建可执行文件。
我们将使用一个包含一个函数的库作为例子:该函数用于解析命令行参数。命令行参数可能是独立的:
-i
也可能是附加的:
-ioptarg
或者是分开作为独立的 argv
元素:
-i optarg
这个库除了函数外,还导出了四个声明:三个整数和一个指向可选参数的指针。如果参数没有附加可选参数,指向可选参数的指针将为 null
。
为了能够解析这些类型的参数,我们编写了以下的 "getopt.c" 文件:
#include <stdio.h> /* for fprintf() and EOF */
#include <string.h> /* for strchr() */
#include "getopt.h" /* consistency check */
/* variables */
int opterr = 1; /* getopt prints errors if this is on */
int optind = 1; /* token pointer */
int optopt; /* option character passed back to user */
char *optarg; /* flag argument (or value) */
/* function */
/* return option character, EOF if no more or ? if problem.
The arguments to the function:
argc, argv - the arguments to the main() function. An argument of "--"
stops the processing.
opts - a string containing the valid option characters.
an option character followed by a colon (:) indicates that
the option has a required argument.
*/
int
getopt (int argc, char **argv, char *opts)
{
static int sp = 1; /* character index into current token */
register char *cp; /* pointer into current token */
if (sp == 1)
{
/* check for more flag-like tokens */
if (optind >= argc || argv[optind][0] != '-' || argv[optind][1] == '\0')
return EOF;
else if (strcmp (argv[optind], "--") == 0)
{
optind++;
return EOF;
}
}
optopt = argv[optind][sp];
if (optopt == ':' || (cp = strchr (opts, optopt)) == NULL)
{
if (opterr)
fprintf (stderr, "%s: invalid option -- '%c'\n", argv[0], optopt);
/* if no characters left in this token, move to next token */
if (argv[optind][++sp] == '\0')
{
optind++;
sp = 1;
}
return '?';
}
if (*++cp == ':')
{
/* if a value is expected, get it */
if (argv[optind][sp + 1] != '\0')
/* flag value is rest of current token */
optarg = argv[optind++] + (sp + 1);
else if (++optind >= argc)
{
if (opterr)
fprintf (stderr, "%s: option requires an argument -- '%c'\n",
argv[0], optopt);
sp = 1;
return '?';
}
else
/* flag value is next token */
optarg = argv[optind++];
sp = 1;
}
else
{
/* set up to look at next char in token, next time */
if (argv[optind][++sp] == '\0')
{
/* no more in current token, so setup next token */
sp = 1;
optind++;
}
optarg = 0;
}
return optopt;
}
/* END OF FILE */
这个代码实现了一个命令行参数解析函数 getopt
,可以处理多种不同类型的命令行选项和参数,返回相应的选项字符或错误代码。
接口文件将是如下的 "getopt.h" 文件:
#ifndef GETOPT_H
#define GETOPT_H
/* 导出的变量 */
extern int opterr, optind, optopt;
extern char *optarg;
/* 导出的函数 */
int getopt(int, char **, char *);
#endif
/* 结束文件 */
至少,程序员需要接口文件来弄清楚如何使用一个库,尽管通常情况下,库的开发者也会编写文档来指导如何使用库。在上面的例子中,文档应该指出,提供的参数 **argv 和 *opts 都不应为 null 指针(否则为什么要使用 getopt 函数呢?)。具体来说,文档通常会说明每个参数的作用,以及在什么条件下可以预期哪些返回值。使用库的程序员通常不关心库的实现 —— 除非实现存在错误,在这种情况下,他们可能会以某种方式提出投诉。
getopts
库的实现和使用该库的程序都应该声明 #include
"getopt.h"
,以便引用相应的接口。现在,库就“链接”到了程序中 —— 即包含 main()
函数的程序。该程序可能会引用多个接口。
在某些情况下,单纯地放置 #include "getopt.h"
可能看起来是正确的,但仍然无法正确链接。这通常意味着库没有正确安装,或者可能需要进行额外的配置。你需要查看编译器或库的文档,了解如何解决这个问题。
头文件中应该包含什么
一般来说,头文件应该包含程序中其他模块“看到”的所有声明和宏定义(预处理器的 #define
)。
可能的声明:
struct
、union
和enum
声明typedef
声明- 外部函数声明
- 全局变量声明
在上面的 getopt.h
示例文件中,声明了一个函数 (getopt
) 和四个全局变量(optind
、optopt
、optarg
和 opterr
)。这些变量在头文件中使用 extern
存储类说明符声明,因为该关键字指定了“实际的”变量存储在其他地方(即 getopt.c
文件中),而不是存储在头文件中。
#ifndef GETOPT_H/#define GETOPT_H
技巧通常被称为“包含保护”。它的作用是,如果 getopt.h
文件在一个翻译单元中被多次包含,编译器只会看到该文件的内容一次。另一种方式是在头文件中使用 #pragma once
,在某些编译器中可以实现相同的效果(#pragma
是一种不跨平台的通用捕获语法)。
将库链接到可执行文件
将库链接到可执行文件的过程因操作系统和使用的编译器/链接器不同而有所不同。在 Unix 中,可以使用 -L
选项指定链接的目标文件目录,并使用 -l
(小写字母 l)选项指定单个库。例如,-lm
选项指定链接 libm
数学库。
参考资料
- C FAQ: "我想知道应该在
.c
文件中放什么,应该在.h
文件中放什么。(.h
文件到底是什么意思?)" - PIClist 讨论串: "在包含许多 C 文件的项目中使用全局变量"
- "如何使用 extern 在 C 中共享变量?"