21世纪C语言 第1章 便利编译配置
使用包管理器
必须获取的包:
- 编译器。必须安装gcc;clang可能也有用。
- gdb,调试器
- Valgrind,测试C内存使用错误。
- gprof,一个分析工具
- make,你永远不需要直接调用编译器
- pkg-config,查找库
- Doxygen,文档生成
- 文本编辑器。Emacs或vim。
- 自动工具:Autoconf,Automake,libtool。
- Git
- Shell替换品,比如Z shell。
一些省去重复造轮子的C库:
- libcURL
- libGLib
- libGSL
- libSQLite3
- libXML2
通向库的路径
|
|
编译器会将math.h和stdio.h文件内容粘贴进代码文件。math.h中的声明并没有说明erf函数做了什么。链接器负责找到erf,你需要告诉链接器-lm找到math库。-l指示一个库需要链接进来。你可以免费使用printf,因为链接器会用隐式的-lc将标准库libc链接进来。
如果使用gcc编译器,完整的命令包括一些额外的标志就像这样:
|
|
一些出名的标志
推荐使用这些编译标志:
- -g,为调试添加符号。
- -std=gnu11,clang-和gcc-特定,指示编译器允许遵守C11和POSIX标准的代码。POSIX标准指定系统中要有c99程序。
- -o3,指示优化等级3,会尝试任何手段编译更快的代码。如果不需要太多优化,也可以使用**-o0**。
- -Wall,添加编译器警告。也可以使用**-w1**,只显示编译器警告,没有附注。-Werror,编译器将会把警告视为错误。
路径
在一个典型配置中,库至少会安装在三个地方:
- 操作系统供应商可能会定义1到2个标准目录来安装库。
- 本地系统管理员可能有一个目录安装不想被供应商覆盖的包。
- 用户在自己主目录可能有库目录。
假设你有一个叫libuseful的库安装在/usr/local目录。你已经把#include <useful.h>写进代码,现在你需要使用下面的命令编译代码:
|
|
- -I将指定路径添加进头文件搜索路径,编译器在搜索路径搜索你包含的头文件。
- -L添加库搜索路径。
- 链接的顺序有关系。如果你有一个名字为specific.o的文件,依赖libbroad库,且libbroad库依赖libgeneral库,那么你需要,gcc specific.o -lbroad -lgeneral。任何其它的顺序都可能会失败。
pkg-config返回已安装库的维护信息。
|
|
回到前面那个命令,当你使用反撇号包含一个命令时,shell会使用其输出替换该命令。
|
|
等价于:
|
|
运行时链接
静态库由编译器通过拷贝库内容链接进可执行程序。共享库在运行时链接进程序,意味着和编译时一样存在库的查找问题。如果是一个在常见位置的库,运行时系统将没有查找库的问题。如果库不在标准路径,则你需要找到一种修改运行时路径查找的方法。
- 如果使用Autotools打包程序,libtool知道如何添加正确的标志,你不需要担心它。
- 当使用gcc,clang或icc基于libpath的库编译程序时,添加: LDADD=-Llibpath -Wl, -Rlibpath到makefile里面。-L标志告诉编译器去哪里查找库以确定符号;-Wl标志传递标志给链接器,链接器将指定-R标志的库嵌入运行时库的查找路径。pkg-config通常不知道运行时路径,因此需要手动输入。
- 运行时,链接器将使用另一个路径查找不在常见位置也没有-Wl,R…指定的库。这个路径在shell的启动脚本里面设置:
|
|
使用Makefile
makefile提供了所有这些无止境的调整的一种解决方案。它基本上是组织的一组变量和单行shell脚本的序列。POSIX标准的make程序读取makefile里面的指令和变量,并将长且单调的命令行组合给我们。
|
|
用法:
- 一次就好:将这几行保存在.c文件同一个目录,并命名为Makefile(GNU Make)。在第一行设置你的程序名,没有.c后缀。
- 每次需要重新编译:输入make。
设置变量
shell和make使用**$指示变量的值。shell使用$var**,而make需要任何变量名长度大于1个字符的变量包含在括号中:$(var)。
有几种方法告诉make变量:
- 调用make之前设置变量并export这个变量。POSIX标准命令行设置CFLAGS变量:export CFLAGS=’-g -Wall -O3’
- 你可以将这些export命令放进shell启动脚本,比如.bashrc或.zshrc。
- 你可以在命令之前赋值设置一个变量。PANTS=kakhi env | grep PANTS。等号两边不能有空格,因为空格是用来区分命令和赋值的。
- 早期的makefile可以在文件头设置变量。在makefile文件里面,等号两边可以有空格。
- make允许在命令行设置变量,独立于shell。
|
|
C语言中的环境变量
|
|
make也提供一些内置变量:
- $@ 所有的目标文件。目标文件是源文件编译完生成的中间文件(.o文件)。
- $* 去掉后缀的目标文件。如果目标文件是prog.o,则**$是prog,且$**.c就是prog.c
- $< 引起目标被触发并编译的文件名。如果我们编译prog.o,可能因为prog.c最近被修改了,所以**$<**就是prog.c。
规则
除了设置变量,makefile的片段具有以下形式:
|
|
如果通过命令make target目标被调用,则依赖被检查。如果目标是一个文件,依赖也全部是文件,且目标比依赖新,则文件是最新的,没有什么事要做。否则,目标的处理被暂停,依赖被运行或生成,可能通过另一个目标,当依赖的脚本都完成了,目标的脚本开始运行。
|
|
在前面简单的makefile里面,我们只有一个目标/依赖/脚本组合。比如:
|
|
P=domath是需要编译的程序,它依赖对象文件addition.o和substration.o。因为addition.o没有作为目标列出来,make使用隐式规则将.c文件编译成.o文件。同样的操作处理substraction.o和domath.o(GNU make隐式假定domath依赖domath.o)。当所有对象被编译时,我们没有脚本规则建立$(P)目标,GNU make填写默认脚本,链接.o文件成可执行程序。
POSIX标准make将.c文件编译成.o文件的默认规则:
|
|
$(CC)变量代表你的C编译器;POSIX标准指定默认CC=c99。$(CFLAGS)设置为之前的标志。$(LDFLAGS)没有设置因此为空。
GNU make将目标文件编译成可执行程序的默认规则:
|
|
回忆一下链接的顺序很重要,因此我们需要两个链接器变量。
|
|
如果想要看完整的make默认规则和内置变量,试一试:make -p > default_rules
这就是游戏规则:查找正确的变量并在makefile里面设置。
- CFLAGS变量是一个根深蒂固的习俗,但是为链接器设置的变量在每个系统都不一样。甚至LDLIBS也不是POSIX标准,它只是GNU make使用。
- CFLAGS和LDLIBS变量是我们指定所有编译器标志并查找和指定库。如果你有pkg-config,使用反撇号调用。
|
|
或者手动指定**-I**,-L和**-l**标志:
|
|
- 在你将一个库和其路径添加进LDLIBS和CFLAGS后,没有理由再去除它。你不会在意最终的可执行程序可能大一点。而且这样也可以makefile在各个工程里面不太需要修改。
- 如果你的程序需要更多C文件,在makefile中添加name.o到OBJECTS。
- 如果你的程序只有一个.c文件,你可能根本不需要makefile。你可以使用下面的方法使用make:
|
|
从源文件使用库
可以通过编译源代码来安装库。下面用GSL(GNU Scientific Library)库作为例子。假设你有root权限:
|
|
如果没有出错的话,GSL就已经安装好了。下面是一个简单的使用gsl库的程序:
|
|
要使用你安装的库,你需要修改你的makefile,取决与你是否有pkg-config,你可以使用其中一个:
|
|
或:
|
|
如果你没有安装在标准位置且没有pkg-config,你需要添加路径:
|
|
从源文件使用库(即使你的系统管理员不允许)
首先创建一个目录,比如:
|
|
接着添加路径:
|
|
在makefile中添加新路径:
|
|
将所要的库安装到指定路径:
|
|
通过嵌入文档编译C程序
你已经看过编译的模式很多次了:
- 设置一个变量代表编译的标志
- 设置一个变量代表链接的标志,每一个你使用的库包括一个-l标志
- 使用make或IDE将变量转换为完整的编译和链接命令。
从命令行包含头文件
gcc和clang有一个方便的标志包含头文件,比如:
|
|
和这句一样:
|
|
-include是编译器特定的。
统一的头文件
为了有用,头文件必须包含typedefs,宏定义和函数声明,且不应该包含没有不会使用的定义或声明。而现在的趋势是节省用户时间,将多个头文件包含进一个头文件。
嵌入文档
嵌入文档是一个POSIX标准shell的特性,你可以用在C,Python,Perl或其他。
|
|
- 嵌入文档是shell的标准特性,因此它应该能在任何POSIX系统上工作。
- “XXXX"是任意你喜欢的字符串;“EOF"很流行,”—–“看起来不错只要顶部和底部的破折号数量相同既可以。当shell看到你选择的字符串为独立的一行,它将停止发送脚本到程序stdin。
- 有一个变体以«-开始,它会删除没一行开头的所有tab字符。
- 作为另一个变体,«XXXX和«“XXXX"不同。前面那个可以插入$shell_variable。
从标准输入编译
|
|