一,gcc基础语法:
基本语法结构:(由以下四部分组成)
gcc -o 可执行文件名
依赖文件集(*.c/*.o)
依赖库文件及其头文件集(由-I或-L与-l指明)
gcc 依赖文件集(*.c/*.o)
依赖库文件及其头文件集(由-I或-L与-l指明)
-o
可执行文件名
注意两点:1. gcc永远在首,2. 库永远在依赖文件(*.c/*.cpp/*.o)之后;
Q:gcc编译时链接库选项问题:
gcc -o test -I. str_out.h -L. -lstr_out main.c: 提示无法通过编译,说不能正确链接库函数
A:改为gcc -o test main.c -I. str_out.h -L. -lstr_out
注:当 静态库(libstr_out.a) 和 动态库(libstr_out.so) 同名时, gcc命令将优先使用动态库,无论是静态还是动态库都是由 *.o 目标文件生成,所以第一步都是生成 *.o 目标文件!
二,静态库生成步骤如下:
步骤一:生成 str_out.o 目标文件
gcc -c str_out.c
注:不加 -o 则生成同名的 *.o 目标文件(str_out.o),加上 -o 可以指定生成任意名的目标文件。
步骤二:生成 libstr_out.a 静态库,Linux规定静态库的命名规则一定是以 lib 开头且以 .a 结尾!
ar -crs
libstr_out.a str_out.o
或
ar -cqs libstr_out.a str_out.o
强调一下:ar后面必先是 -crs 或 -cqs 引导的 lib*.a 静态库名,再是
*.o文件名,这个格式是固定的!
如果写成 ar str_out.o -crs libstr_out.a 或 ar str_out.o -cqs
libstr_out.a 都会报如下错误:
ar: two different operation options specified
注:-c -> 小写字母,如果该库不存在,则有些版本的 ar 必须指定 c 选项,才会进行创建,GNU
ar 不需要。
-r -> 小写字母,将参数
member 中指定的成员插入到归档文件中,替换掉归档文件中原来的同名成员。
即先删除原来同名的成员,然后将新成员插入到归档文件中。
默认情况下,新插入的成员会被添加到归档文件的末尾,但可以通过选项`a’, `b’,
或`i’指明插入的位置。
-a
-> 在归档文件中一个已经存在的成员后面增加由参数 member 给出的新文件。
如果使用选项`a’,则应该为命令行参数 member 指定一个已经存在的成员名(即给出参数
relpos)。
-b ->
在归档文件中一个已经存在的成员前面增加由参数 member 给出的新文件。
如果使用任选项`b’,则应该为命令行参数 member
指定一个已经存在的成员名(即给出参数 relpos)。
-i ->
在归档文件中一个已经存在的成员前面增加一个新的文件。
如果使用任选项i,则应该为命令行参数 member指定一个已经存在的成员名
(即给出参数
relpos,类似于选项`b’)。
而不检查是否要进行替换,也不更新符号表索引。
-s -> 小写字母,写入一个目标文件索引到归档文件中,或者更新一个存在的目标文件索引。 甚至对于没有任何变化的归档文件也作该动作(等同于对该库做ranlib)。
如果写成这样,r与q共存的话,
ar str_out.o -crqs libstr_out.a
则会出现如下错误提示:
ar: two different operation options specified
以上两步其实已经创建了一个静态库!
步骤三:将静态库合并入可执行程序
gcc main.c -o teststaticlib -L. -l str_out
也可写成,如下:
gcc -c main.c -o main.o
gcc main.o -L . -l str_out -o teststaticlib
注:-L -> 大写字母,指定静态库的查找位置,-L后面的.(点)表示静态库在当前目录下查找。
-l -> 小写字母,指定静态库名,由于静态库的命名规则是以lib开头且以.a结尾(lib*.a),
故此,lib与.a一定要忽略,如上所示。
如果要合并入多个静态库,则每个静态库名前都要加-l。
-L与.之间空格可有可无,-l与静态库名之间空格可有可无。
nm *.a 命令可以用来列出静态库文件中的符号清单。
三、动态库生成步骤如下:
步骤一:生成 str_out.o 目标文件
gcc -c str_out.c -o str_out.o
步骤二:生成 libstr_out.so 动态库文件,Linux规定其动态库命名方式为“lib*.so.*”。
在这个命名方式中,第一个*表示动态链接库的库名,第二个*通常表示该动态库的版本号,
也可以没有版本号!例如:libc-2.9.so、libc.so.6、libcap.so.2.11。
gcc -shared -fPIC -Wall str_out.o -o
libstr_out.so
步骤三:将生成的动态库放到系统动态库默认目录(/usr/lib)中去,以便系统可以搜索到它。
sudo cp libstr_out.so /usr/lib
注:修改系统目录需要 root 用户权限,
步骤四:链接动态库并生成可执行文件
gcc -c main.c -o main.o
gcc main.o -l str_out -o testdynamiclib_1
也可以省略第三步用 -Wl,-rpath=./ -L . 在编译时记录运行时搜索动态库的路径,如下:
gcc -c main.c -o main.o
gcc -o testdynamiclib_2 main.o -L . -l str_out -Wl,-rpath,./
写成如下这样也可以:
gcc main.o -Wl,-rpath,./ -L . -l str_out -o testdynamiclib_2
或
gcc main.o -Wl,-rpath=./ -L . -l str_out -o testdynamiclib_2
强调:1) -Wl,-rpath,动态库路径 或 -Wl,-rpath=动态库路径 这两种写法都可以!
都是在elf文件中增加记录动态库的搜索路径(除系统默认的外,再搜索指定的)。
2) 如果少了-L 动态库路径 来指定动态库路径同样编译不过去!
注:当指定多个动态库搜索路径时,路径之间用冒号":"分隔。
用 readelf -d 可行文件名 命令查看ELF文件的动态节(Dynamic Section)。(如下面所示)
对比 testdynamiclib_1 和 testdynamiclib_2 的结果我们可以发现,
testdynamiclib_2 中多出来了RPATH项,指定”Library rpath: [./]”,与可执行文件同一个目录。
通过这种方式,我们可以用非常小的代价(仅增加几乎可以忽略的空间开销),
对每个ELF文件都指定最优化的搜索路径,达到提升性能的目的。这是我们比较推荐的一种方法。
readelf -d testdynamiclib_1
Dynamic section at offset 0xf18 contains 22 entries:
Tag
0x00000001 (NEEDED) Shared library: [libstr_out.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000c (INIT) 0x8048380
0x0000000d (FINI) 0x804856c
0x00000004 (HASH) 0x8048168
0x6ffffef5 (GNU_HASH) 0x80481a8
0x00000005 (STRTAB) 0x8048294
0x00000006 (SYMTAB) 0x80481e4
0x0000000a (STRSZ) 147 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x8049ff4
0x00000002 (PLTRELSZ) 24 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048368
0x00000011 (REL) 0x8048360
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x8048340
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048328
0x00000000 (NULL) 0x0
readelf -d testdynamiclib_2
Dynamic section at offset 0xf10 contains 23 entries:
Tag Type Name/Value
0x00000001 (NEEDED) Shared library: [libstr_out.so]
0x00000001 (NEEDED) Shared library: [libc.so.6]
0x0000000f (RPATH) Library rpath: [./]
0x0000000c (INIT) 0x8048380
0x0000000d (FINI) 0x804856c
0x00000004 (HASH) 0x8048168
0x6ffffef5 (GNU_HASH) 0x80481a8
0x00000005 (STRTAB) 0x8048294
0x00000006 (SYMTAB) 0x80481e4
0x0000000a (STRSZ) 150 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x8049ff4
0x00000002 (PLTRELSZ) 24 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x8048368
0x00000011 (REL) 0x8048360
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x8048340
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x804832a
0x00000000 (NULL) 0x0
ldd 可执行文件名 -> 查看库链接状况;
objdump <选项> 文件名 -> 对象文件信息;
还可以指定要链接的动态库所在的路径,如下:
gcc -c main.c -o main.o
gcc main.o -l str_out -B ./ -o testdynamiclib_2
上下两句意思相同,都可编译通过,但运行时找不到动态库!
gcc main.o -L . -l str_out -o testdynamiclib_2
注:-B -> 大写字母,指定要链接的动态库所在的路径,./ 表示为当前目录。
但是存在一个问题是,编译过去了,但运行时还是找不到动态库!
运行时报错:
./testdynamiclib_2: error while loading shared libraries: libstr_out.so: cannot open shared object file: No such file or directory
还可以添加系统变量来指定新的搜索目录路路,如下:
export LD_LIBRARY_PATH=./
注:一旦LD_LIBRARY_PATH被设定,则在这个环境变量生效的范围之内,
所有其他的ELF可执行程序也会按照这个顺序去搜索动态库,这样势必会造成搜索时的一些浪费。
删除添中的系统变量,如下:
unset LD_LIBRARY_PATH
也还可以用 LD_PRELOAD 环境变量,它允许你定义在程序运行前优先加载的动态链接库。
这个功能主要就是用来有选择性的载入不同动态链接库中的相同函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,甚至覆盖正常的函数库。
一方面,我们可以以此功能来使用自己的或是更好的函数(无需别人的源码);
另一方面,我们也可以以向别人的程序注入恶意程序,从而达到那不可告人的目的;
在一些UNIX版本上,如果你想要使用LD_PRELOAD环境变量,你需要有root权限。
通过设置执行文件的setgid / setuid标志。
在有SUID权限的执行文件,系统会忽略LD_PRELOAD环境变量。
也就是说,如果你有以root方式运行的程序,最好设置上SUID权限。(如:chmod 4755 daemon)
可以通过下面的实例来了解LD_PRELOAD环境变量的使用方法:
1)动态库程序编写,在Windows系统下的执行文件格式是PE格式,动态库需要一个DllMain函数作为初始化的人口,通常在导出函数的声明时 需要有_declspec(dllexport)关键字。Linux下的gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要到函数做特别 声明,编写比较方便。
2)动态库编译,在windows系统下面,有方便的调试编译环境,通常不用自己去编写makefile文件,但在linux下面,需要自己动手去编写makefile文件,因此,必须掌握一定的makefile编写技巧,另外,通常Linux编译规则相对严格。
3)动态库调用方面,Windows和Linux对其下编制的动态库都可以采用显式调用或隐式调用,但具体的调用方式也不尽相同。
4)动态库输出函数查看,在Windows中,有许多工具和软件可以进行查看DLL中所输出的函数,例如命令行方式的dumpbin以及VC++工具 中的DEPENDS程序。在Linux系统中通常采用nm来查看输出函数,也可以使用ldd查看程序隐式链接的共享对象文件。
5)对操作系统的依赖,这两种动态库运行依赖于各自的操作系统,不能跨平台使用。因此,对于实现相同功能的动态库,必须为两种不同的操作系统提供不同的动态库版本。
四,Q&A:
1, -I选项的深度探讨:
l 选项的位置是有意义的,
gcc在处理 -l
选项链接的库的时候,只会查找出现在它前面的文件中所需要链接的符号,如:
gcc -o foo file1.c -lm
file2.c
中 gcc 处理 m 库时只会链接 file1.c 中用到的库函数,
而如果 file2.c 中也用到 m 库,它是不能正确链接的。
所以一般将 -l 选项放在 依赖文件集(*.c/*.cpp/*.o) 的后面。
多个库文件要链接时,一定要每个库文件前都有一个 -l 选项!
也可以写成如下形式:
gcc -o test main.c -I. str_out.h -L. ./libstr_out.a
因为,指定 -l 选项 和 指定文件名 的唯一区别是:
-l 选项用 lib 和 .a 或 .so 把 library 包裹起来,而且搜索一些目录。
(默认的库文件位于/usr/lib/或/usr/local/lib/目录中)
原文:http://gcc.gnu.org/onlinedocs/gcc-4.4.2/gcc/Link-Options.html#Link-Options
-llibrary
-l library <-- 是不推荐的
当链接时搜索名为 library 的库文件。
(The second alternative with
the library as a separate argument is only for POSIX compliance and is not recommended.)
(有第二种供选择的方式,即参数 库文件名 是与-l分开的,
这种方式只有 POSIX 遵守,并且是不推荐的)
It makes a difference where in the command you write this option;
the linker searches and processes libraries and object files in the order they are specified.
你可以在命令中使用选项以上两种不同形式,链接器搜索处理库和对象文件的顺序是指定的。
Thus, `foo.o -lz bar.o' searches library `z' after file foo.o but before bar.o.
因此,‘foo.o -lz bar.o’中搜索名为z的库在文件foo.o之后,但是在文件bar.o之前。
If bar.o refers to functions in `z', those functions may not be loaded.
如果bar.o引用了名为z的库中的函数,这些函数是加载不上的。
The linker searches a standard list of directories for the library,
which is actually a file named liblibrary.a.
连接器实际上是以lib与.a括起库名在标准搜索目录中寻找库文件。
The linker then uses this file as if it had been specified precisely by name.
链接器然后使用精确地明确说明名字的这个文件。
The directories searched include several standard system directories
plus any that you specify with -L.
目录搜索除了若干系统标准目录外,还包括用户以 -L 选项指定的路径。
Normally the files found this way are library files—archive files whose
members are object files.
一般说来用这个方法找到的文件是库文件,即由目标文件组成的归档文件。
The linker handles an archive file by scanning through it for members
which define symbols that have so far been referenced but not defined.
连接器处理归档文件是通过从头至尾扫描归档文件中一直是引用已定义符号的成员。
But if the file that is found is an ordinary object file, it is linked in the usual fashion.
但是,如果连接器找到的是一个普通的目标文件,就用平常的方式把这个目标文件连接进来。
The only difference between using an -l option and specifying a file name is that
-l surrounds library with `lib' and `.a' and searches several directories.
在指定文件名和使用一个 -l 选项之间的唯一区别是,
-l 选项用 lib 和 .a 把 library 包裹起来并搜索若干目录。