Makefile和gcc编译相关

成果都是别人的,只是按我的理解重新摘录,大言不惭说原创,望谅解。

一、Makefile

1.1 makefile规则

转自百度百科
让我们先来粗略地看一看Makefile的规则。
target … : prerequisites …
command


目标:依赖
执行指令 …
target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label)。
① prerequisites就是,要生成那个target所需要的文件或是目标。
② command也就是make需要执行的命令。(任意的Shell命令)
这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行(command一定要以Tab键开始,否则编译器无法识别command),减少重复编译,提高了其软件工程管理效率。

1.2 预定义变量

CC: C编译器的名称,默认值为 cc。
CPP: C 预编译器的名称,默认值为 $(CC) -E。
CPPFLAGS: C 预编译的选项。
CCFLAGS: C 编译器的选项。

CXX: C++编译器的名称,默认值为 g++。
CXXFLAGS: C++ 编译器的选项。
AR: 归档维护程序的名称,默认值为 ar。
ARFLAGS: 归档维护程序的选项。
AS: 汇编程序的名称,默认值为 as。
ASFLAGS: 汇编程序的选项。

$@ 表示目标文件
$^ 表示所有的依赖文件
$< 表示第一个依赖文件
$? 表示比目标还要新的依赖文件列表

$* 不包含扩展名的目标文件名称。
$+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。
$% 如果目标是归档成员,则该变量表示目标的归档成员名称。
例如,如果目标名称为mytarget.so (image.o),则 @为mytarget.so,而
% 为 image.o。

1.3 := ?= += =的区别

这部分转自
https://www.cnblogs.com/wanqieddy/archive/2011/09/21/2184257.html
= 是最基本的赋值
:= 是覆盖之前的值
?= 是如果没有被赋值过就赋予等号后面的值
+= 是添加等号后面的值
1)、“=”
make会将整个makefile展开后,再决定变量的值。也就是说,变量的值将会是整个makefile中最后被指定的值。看例子:

x = foo
y = $(x) bar
x = xyz

在上例中,y的值将会是 xyz bar ,而不是 foo bar 。
2)、“:=”
“:=”表示变量的值决定于它在makefile中的位置,而不是整个makefile展开后的最终值。

x := foo
y := $(x) bar
x := xyz

在上例中,y的值将会是 foo bar ,而不是 xyz bar 了。

1.4 wildcard等函数

1)、wildcard : 扩展通配符
2)、notdir : 去除路径
3)、patsubst :替换通配符
例子:
建立一个测试目录,在测试目录下建立一个名为sub的子目录

$ mkdir test
$ cd test
$ mkdir sub

在test下,建立a.c和b.c2个文件,在sub目录下,建立sa.c和sb.c2 个文件。

建立一个简单的Makefile

src=$(wildcard *.c ./sub/*.c)
dir=$(notdir $(src))
obj=$(patsubst %.c,%.o,$(dir) )

all:
@echo $(src)
@echo $(dir)
@echo $(obj)
@echo "end"

执行结果分析:
第一行输出
a.c b.c ./sub/sa.c ./sub/sb.c
wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

第二行输出
a.c b.c sa.c sb.c
notdir把展开的文件去除掉路径信息

第三行输出
a.o b.o sa.o sb.o

在$(patsubst %.c,%.o,$(dir) )中,
patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o。
或者可以使用obj=$(dir:%.c=%.o),效果也是一样的。

二、gcc

2.1 Linux静态库.a与动态库.so的生成与区别

转自Linux静态库.a与动态库.so的生成与区别
如果有公司需要使用你们产品的一部分功能(通过代码调用这些功能),如果不想提供源代码,那么就可以通过封装成库文件的形式提供给对方使用。
.o文件 :二进制目标文件,可用于打包成库文件也可以链接生成可执行文件;
c文件编译后链接,生成可执行文件。

gcc  test1.c test2.c test3.c test_main.c -o test_main
./test_main

将.o目标文件链接生成可执行文件

gcc -c  test1.c test2.c test3.c test_main.c//编译成.o目标文件
gcc  test1.o test2.o test3.o test_main.o -o test_main_1//把.o文件链接成可执行文件
./test_main_1

1).a文件 :静态库文件,静态库在编译时已经被链接到目标代码中,运行程序不依赖该静态库文件;
优点:将程序使用的函数的机器码复制到最终的可执行文件中,提高了运行速度;如果库函数改变,整个程序需要重新编译
缺点:所有需用到静态库的程序都会被添加静态库的那部分内容,使得可执行代码量相对变多,占用内存和硬盘空间.

ar rc libtest.a test1.o test2.o test3.o//把.o文件打包成.a的库文件 
gcc test_main.c -L. -ltest -o test_main_a//链接生成可执行文件 
./test_main_a//运行测试程序 
rm libtest.a //删除静态库文件 
./test_main_a//同样正常运行程序

2).so文件: 动态库文件,在程序运行的时候载入动态库文件,程序要正常运行必须依赖于它需要使用的动态库文件;
优点:只有在程序执行的时候, 那些需要使用的函数代码才被拷贝到内存中。动态库在内存中可以被被多个程序使用,又称之为共享库,节约了内存和磁盘空间,以时间效率去换取空间效率;当调用接口没改变,库文件发生改变重新编译,而调用的主程序可不用重新编译;
缺点:运行速度不及静态库文件;
静态库与动态库的选取使用,请结合自己的场景进行取舍.
3).so库文件的封装以及使用

OBJS:=adTorgb.cpp.o  descriptors.c.o  error.c.o  linux.c.o  satusbimage.cpp.o  usb.c.o

#把所有[.c]文件编译成[.o]文件
#-fPIC; 代表编译为位置独立的代码,满足了不同的进程对所加载动态库的共享;
#-c; 表示只编译源文件但不链接;
#$<; 表示所搜索到与第一个相匹配的文件,即第一个[.c]文件;
#-o; 指定输出文件名;
#$@; 与[.c]文件相对应的[.o]文件;
#-I.; 需用到的头文件在本目录中找.
%.c.o:%.c 
        gcc -fPIC -c $< -o $@ -I.

%.cpp.o:%.cpp
        g++ -fPIC -c $< -o $@ -I.
        
#-shared: 该选项指定生成动态连接库
all:$(OBJS)
        @echo "Compile..."
        g++ -shared -fpic -o libsatusb.so $(OBJS)
        @echo "End"

clean:
        rm -fr *.o *.so

测试函数文件夹中的Makefile

all:
		#-L./lib: 编译时到当前路径的lib文件夹中去需找libsatusb.so库文件
        gcc satimagetest.c -L./lib -lsatusb -o satimagetest
		#如果要运行编译完成的可执行文件,必须得设置下环境变量(执行程序时会去链
		#接这个.so库文件,如果不设置环境变量,就找不到该库文件,程序执行失败),
		#或者把生成的.so库文件拷贝到已设置的路径下(可以查环境变量
		#LD_LIBRARY_PATH,但移植性不高)。
#export LD_LIBRARY_PATH=/opt/satImage/lib/:$LD_LIBRARY_PATH

2.2 gcc常用选项及 -I -L -l的区别

1) 转自GCC简介
①许多情况下,头文件和源文件会单独存放在不同的目录中。例如,假设存放源文件的子目录名为./src,而包含文件则放在层次的其他目录下,如./inc。当我们在./src 目录下进行编译工作时,如何告诉GCC到哪里找头文件呢?方法如下所示:

gcc test.c –I../inc -o test

上面的命令告诉GCC包含文件存放在./inc 目录下,在当前目录的上一级。如果在编译时需要的包含文件存放在多个目录下,可以使用多个-I 来指定各个目录。
②警告功能
当GCC在编译过程中检查出错误的话,它就会中止编译;但检测到警告时却能继续编译生成可执行程序,因为警告只是针对程序结构的诊断信息,它不能说明程序一定有错误,而是存在风险,或者可能存在错误。虽然GCC提供了非常丰富的警告,但前提是你已经启用了它们,否则它不会报告这些检测到的警告。
在众多的警告选项之中,最常用的就是-Wall选项。该选项能发现程序中一系列的常见错误警告,该选项用法举例如下:

gcc -Wall test.c -o test

该选项相当于同时使用了下列所有的选项:

◆unused-function:遇到仅声明过但尚未定义的静态函数时发出警告。
◆unused-label:遇到声明过但不使用的标号的警告。
◆unused-parameter:从未用过的函数参数的警告。
……
2)转自多个文件目录下Makefile的写法

 gcc -o hello hello.c -I /home/hello/include -L /home/hello/lib -lworld

上面这句表示在编译hello.c时:
-I /home/hello/include表示将/home/hello/include目录作为第一个寻找头文件的目录,寻找的顺序是:/home/hello/include–>/usr/include–>/usr/local/include。
-L /home/hello/lib表示将/home/hello/lib目录作为第一个寻找库文件的目录,寻找的顺序是:/home/hello/lib–>/lib–>/usr/lib–>/usr/local/lib。
-lworld表示在上面的lib的路径中寻找libworld.so动态库文件(如果gcc编译选项中加入了“-static”表示寻找libworld.a静态库文件)

2.3 gcc同时使用静态库和动态库链接

转自GCC同时使用静态库和动态库链接
1) 在应用程序需要连接外部库的情况下,linux默认对库的连接是使用动态库,在找不到动态库的情况下再选择静态库。使用方式为:

gcc test.cpp -L. -ltestlib

如果当前目录有两个库 libtestlib. so libtestlib.a,则肯定是连接 libtestlib. so。如果要指定为连接静态库则使用:

gcc test.cpp -L. -static -ltestlib

使用静态库进行连接。

当对动态库与静态库混合连接的时候,使用-static会导致所有的库都使用静态连接的方式。这时需要作用-Wl的方式:

gcc test.cpp -L. -Wl,-Bstatic -ltestlib  -Wl,-Bdynamic -ltestdll 

另外还要注意系统的运行库使用动态连接的方式,所以当动态库在静态库前面连接时,必须在命令行最后使用动态连接的命令才能正常连接,如:

gcc test.cpp -L. -Wl,-Bdynamic -ltestdll -Wl,-Bstatic -ltestlib  -Wl,-Bdynamic

最后的-Wl,-Bdynamic表示将缺省库链接模式恢复成动态链接。
2)
场景是这样的。我在写一个Nginx模块,该模块使用了MySQL的C客户端接口库libmysqlclient,当然mysqlclient还引用了其他的库,比如libm, libz, libcrypto等等。对于使用mysqlclient的代码来说,需要关心的只是mysqlclient引用到的动态库。大部分情况下,不是每台机器都安装有libmysqlclient,所以我想把这个库静态链接到Nginx模块中,但又不想把mysqlclient引用的其他库也静态的链接进来。
  我们知道gcc的-static选项可以使链接器执行静态链接。但简单地使用-static显得有些’暴力’,因为他会把命令行中-static后面的所有-l指明的库都静态链接,更主要的是,有些库可能并没有提供静态库(.a),而只提供了动态库(.so)。这样的话,使用-static就会造成链接错误。

之前的链接选项大致是这样的:
CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -lmysqlclient -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

##修改过:
CORE_LIBS="$CORE_LIBS -L/usr/lib64/mysql -Wl,-Bstatic -lmysqlclient\ -Wl,-Bdynamic -lz -lcrypt -lnsl -lm -L/usr/lib64 -lssl -lcrypto"

其中用到的两个选项:-Wl,-Bstatic和-Wl,-Bdynamic。这两个选项是gcc的特殊选项,它会将选项的参数传递给链接器,作为链接器的选项。比如-Wl,-Bstatic告诉链接器使用-Bstatic选项,该选项是告诉链接器,对接下来的-l选项使用静态链接;-Wl,-Bdynamic就是告诉链接器对接下来的-l选项使用动态链接。

附有用的网址

GCC编译器30分钟入门教程
gcc/g++ 链接库的编译与链接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章