一、静态库和动态库
定义
根据链接时期的不同,库有静态库和动态库之分。
静态库是在链接阶段被链接的,所以生成的可执行文件就不受库的影响了,即使库被删除了,程序依然可以成功运行。
有别于静态库,动态库的链接是在程序执行的时候被链接的。所以,即使程序编译完,库仍须保留在系统上,以供程序运行时调用。
(TODO:链接动态库时链接阶段到底做了什么)
两者的优缺点
链接静态库其实从某种意义上来说也是一种粘贴复制,只不过它操作的对象是目标代码而不是源码而已。因为静态库被链接后库就直接嵌入可执行文件中了,这样就带来了两个问题。
首先就是系统空间被浪费了。这是显而易见的,想象一下,如果多个程序链接了同一个库,则每一个生成的可执行文件就都会有一个库的副本,必然会浪费系统空间。
而动态库的出现正弥补了静态库的以上弊端。因为动态库是在程序运行时被链接的,所以磁盘上只须保留一份副本,因此节约了磁盘空间。如果发现了bug或要升级也很简单,只要用新的库把原来的替换掉就行了。
正因为动态库在程序运行时被链接,故程序的运行速度和链接静态库的版本相比必然会慢。目前链接程序在链接时一般是优先链接动态库的,除非用-static参数指定链接静态库。
二、GCC 介绍
gcc 作为编译工具,主要在 Linux 操作系统中使用,可以编译 C、C++、Object-C、JAVA 等语言。
编译的流程:
1)预处理 Pre-Processing
2)编译 Compiling
3)汇编 Assembling
4)链接 Linking
GCC编译选项
编译过程中可以带编译选项,选择编译过程:
1) -c :指编译,不链接,生成目标文件“.o”。
2) -S :只编译,不汇编,生成汇编代码“.S”。
3) -E :只进行预编译/预处理,不做其他处理。
4) -o file:把输出文件输出到file里。
5) -g :在可执行程序中包含标准调试信息。
6) -v :打印出编译器内部编译各过程的命令行信息和编译器的版本。7) -I dir :在头文件的搜索路径列表中添加dir目录
8) -L dir :在库文件的搜索路径列表中添加dir目录
9) -static :连接静态库(静态库也可以用动态库链接方式链接)
10) -llibrary :连接名为library的库文件(显示指定需要链接的动态库文件)
生成静态库
gcc -c div.c //生成div.o文件
ar -cr libdiv_sub.a div.o sub.o //生成libdiv_sub.a文件
使用静态库生成可执行文件:
gcc main.c -o main_1.exe -L. -ldiv_sub -I ../add/ -I ../sub/ -I ../mul -I ../div/
-L 表示要使用的静态库的目录,这和前面所讲的 -I (大写 i,指明头文件的目录)差不多,就是用来告诉编译器去哪里找静态库。
因为可能-L所指明的目录下有很多静态库,所以除了要告诉去哪里找之外,还要告诉编译器找哪一个静态库,此时就要用到 -l (小写L )了,它用来说明链接的时候要用到哪个静态库。注意:
1. 注意是使用 -ldiv_sub,而不是 -llibdiv_sub ,这是因为编译器会自动在库中添加 lib 前缀和 .a 后缀。
2. 要把 -l 放到命令的尽可能后的位置,必须放到源文件的后面。
生成动态库
gcc -shared -fPIC -o libadd_mul.so add.o mul.o
使用动态库生成可执行文件:
gcc main.c -o main_2.exe ./libadd_mul.so -I ../add/ -I ../sub/ -I ../mul -I ../div/
使用静态库和动态库一起生成可执行文件:
gcc main.c -o main.exe -L. ./libadd_mul.so -ldiv_sub -I ../add/ -I ../sub/ -I ../mul -I ../div/
或者把动态库放到/usr/lib下:
gcc main.c -o main.exe -L. -ladd_mul -ldiv_sub -I ../add/ -I ../sub/ -I ../mul -I ../div/
三、gdb调式
生成调式信息:
gcc -g
例如:gcc -g main_add.c -o main_add.exe ./libadd.so -I ../add
使用gdb命令开始调试:
gdb ./main_add.exe
四、Makefile 语法介绍
?= 、+= 、:= 的含义
FOO ?= bar
其含义是,如果 FOO 没有被定义过,那么变量 FOO 的值就是“bar”,如果 FOO 先前被定义过,那么这条语将什么也不做,其等价于:
ifeq ($(origin FOO), undefined)
FOO = bar
endif
使用“+=”操作符给变量追加值,如:
objects = main.o foo.o bar.o utils.o
objects += another.o
于是,$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)
使用“+=”操作符可以模拟为下面的这种例子:
objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o
所不同的是用“+=”更为简洁。
如果变量之前没有定义过,那么“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:
variable := value
variable += more
等价于:
variable := value
variable := $(variable) more
wildcard、patsubs、nodir的含义
“wildcard”,其用法是:$(wildcard PATTERN...) 。在Makefile中,它被展开为已经存在的、使用空格分开的、匹配此模式的所有文件列表。如果不存在任何符合此模式的文件,函数会忽略模式字符并返回空。需要注意的是:这种情况下规则中通配符的展开和其他情况下匹配通配符的区别。
一般用法:我们可以使用“$(wildcard *.c)”来获取工作目录下的所有的.c文件列表。
复杂一些用法:可以使用“$(patsubst %.c,%.o,$(wildcard *.c))”,首先使用“wildcard”函数获取工作目录下的.c文件列表;之后将列表中所有文件名的后缀.c替换为.o。这样我们就可以得到在当前目录可生成的.o文件列表。因此,在一个目录下,可以使用如下内容的Makefile来将工作目录下的所有.c文件进行编译,并最后连接成为一个可执行文件:
#举例
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects)
cc -o foo $(objects)
这里我们使用了make的隐含规则来编译.c的源文件
变量替换引用
对于一个已经定义的变量,可以使用“替换引用”将其值中的后缀字符(串)使用指定的字符(字符串)替换。格式为“$(VAR:A=B)”(或者“${VAR:A=B}”),
意思是,替换变量“VAR”中所有“A”字符结尾的字为“B”结尾的字。“结尾”的含义是空格之前(变量值多个字之间使用空格分开)。而对于变量其它部分的“A”字符不进行替换。
例如:
foo := a.o b.o c.o
bar := $(foo:.o=.c)
在这个定义中,变量“bar”的值就为“a.c b.c c.c”。使用变量的替换引用将变量“foo”以空格分开的值中的所有的字的尾字符“o”替换为“c”,其他部分不变。
如果在变量“foo”中如果存在“o.o”时,那么变量“bar”的值为“a.c b.c c.c o.c”而不是“a.c b.c c.c c.c”。
实战演练
本地写了一个小demo,生成四个目标文件,打包成静态库和动态库,给main.c调用。
文件目录如下:
执行过程:
生成文件:
执行main.exe:
main.c 代码:
Makefile脚本代码:
static_obj += $(OBJECT_PATH)/sub.o $(OBJECT_PATH)/div.o
dynamic_obj += $(OBJECT_PATH)/add.o $(OBJECT_PATH)/mul.o#获取makefile的绝对路径
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
pro_path:=$(shell dirname $(mkfile_path))
$(warning $(mkfile_path))
$(warning $(pro_path))HFLAGS += -I add/ -I sub/ -I mul/ -I div/
BINARY_PATH ?= out/bin
LIB_PATH ?= out/lib
OBJECT_PATH ?= out/objs
TARGET1 ?= $(BINARY_PATH)/main_1.exe
TARGET2 ?= $(BINARY_PATH)/main_2.exe
TARGET ?= $(BINARY_PATH)/main.exe#创建目录
$(BINARY_PATH) $(OBJECT_PATH) $(LIB_PATH):
mkdir -p $@#生成目标文件
$(OBJECT_PATH)/add.o : add/add.c
cc -c add/add.c -o $(OBJECT_PATH)/add.o
$(OBJECT_PATH)/sub.o : sub/sub.c
cc -c sub/sub.c -o $(OBJECT_PATH)/sub.o
$(OBJECT_PATH)/mul.o : mul/mul.c
cc -c mul/mul.c -o $(OBJECT_PATH)/mul.o
$(OBJECT_PATH)/div.o : div/div.c
cc -c div/div.c -o $(OBJECT_PATH)/div.o#生成动态库
$(LIB_PATH)/libadd_mul.so : $(dynamic_obj)
cc -shared -fPIC -o $(LIB_PATH)/libadd_mul.so $(OBJECT_PATH)/add.o $(OBJECT_PATH)/mul.o#生成静态库
$(LIB_PATH)/libdiv_sub.a : $(OBJECT_PATH)/div.o $(OBJECT_PATH)/sub.o
ar -cr -o $(LIB_PATH)/libdiv_sub.a $(OBJECT_PATH)/div.o $(OBJECT_PATH)/sub.o
#使用静态库生成可执行文件:
$(TARGET1) : main.c $(LIB_PATH)/libdiv_sub.a
cc main.c -o $(BINARY_PATH)/main_1.exe -L out/lib/ -ldiv_sub $(HFLAGS)
#使用动态库生成可执行文件:
$(TARGET2) : main.c $(LIB_PATH)/libadd_mul.so
cc main.c -o $(TARGET2) $(pro_path)/out/lib/libadd_mul.so $(HFLAGS)
#使用静态库和动态库一起生成可执行文件:
$(TARGET) : main.c $(LIB_PATH)/libdiv_sub.a $(LIB_PATH)/libadd_mul.so
cc main.c -o $(TARGET) $(pro_path)/out/lib/libadd_mul.so -L out/lib/ -ldiv_sub $(HFLAGS).PHONY: all clean prepare build hello post-build
all: hello prepare build post-build
clean: ;rm -rf out
hello: ;@echo ==== start, $(shell date) ====
prepare: $(BINARY_PATH) $(OBJECT_PATH) $(LIB_PATH)
build: $(TARGET)
post-build: ;@echo ==== done, $(shell date) ====