gcc


原文链接: gcc

linux ldconfig命令,环境变量文件配置详解 - winycg的博客 - CSDN博客

--start-group和--end-group 解决 ld 链接 error

原因就是链接多个静态库,且这些静态库之间有依赖时,静态库在command line的顺序不对

举例:假设liba.a 依赖于libb.b,目标test需要liba和libb,那么生成test的命令应该为

gcc -o test -la -lb 而不是gcc -o test -lb -la

也可以使用ld的 --start-group xxx --end-group来将所有相互依赖的静态库放到xxx位置,只有就能保证search多次了

man ld

The linker will search an archive only once, at the location where it is specified on the command line. If the archive defines a symbol whichwas undefined in some object which appeared before the archive on the command line, the linker will include the appropriate file(s) from the archive. However, an undefined symbol in an object appearing later on the command line will not cause the linker to search the archive again.

See the -( option for a way to force the linker to search archives multiple times.

-( archives -)
--start-group archives --end-group

The archives should be a list of archive files.  They may be either explicit file names, or -l options.
The specified archives are searched repeatedly until no new undefined references are created.  Normally, an archive is searched only once in the order that it is specified on the command line.  If a symbol in that archive is needed to resolve an undefined symbol referred to by an object in an archive that appears later on the command line, the linker would not be able to resolve that reference.  By grouping the archives, they all be searched repeatedly until all possible references are resolved.
Using this option has a significant performance cost.  It is best to use it only when there are unavoidable circular references between two or more archives.

链接错误:

        有时候链接时,明明已经将所有的依赖库都加进去了,还是莫名其妙的报ld error,原因可能就是

        1. 上面说的依赖库是静态库时,有先后顺序,如果加上--start-group和--end-group就可以了。

        2. 还有可能是,你编译出来的库和你使用的库的版本不一样,导致接口有可能不用,即你使用的库的头文件和你编译依赖库的头文件不一样。不要想当然的认为一样,最好用用beyondcompare等工具比较一下
————————————————
版权声明:本文为CSDN博主「fingding」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/fingding/java/article/details/39547123

为什么使用静态编译方式编译应用程序无法使用动态库 【现象】

客户 A 现行文件系统/执行程序都是使用静态编译方式编译,以至于不能使用版本发布 的动态库。
【分析】
当前 ARM-Linux-GCC 提供了 3 种编译方式,分别是静态编译,动态编译,半静态编

  1. 静态编译(-static -pthread -lrt -ldl)将会将 libc, libpthread, librt, libdl 都编译到执行程 序中,这样的编译方式将会不依赖任何系统动态库(即可独立执行),但无法使用 动态库系统。
  2. 动态编译(普通编译)将会采取链接系统库的方式去链接/lib 目录下的系统动态库, 这样编译出来的程序需要依赖系统动态库,优点是系统动态库可以被多个可执行 程序共用,如/bin 目录下的 busybox,himount 等。
  3. 半静态编译(-static-libgcc -static-libstdc++ -L. -pthread -lrt -ldl)则会将 gcc 以及 stdc++ 编译到可执行程序中去,其他系统库依然依赖系统动态库。这种编译方式,可以 使用动态库系统,但是依然需要在系统目录下放置 libc, libpthread, librt, libdl 等文 件。
    【解决】
    采取动态编译方式,在/lib 目录下放置动态库依赖的系统文件 ld-uClibc.so.0, libc.so.0,
    libpthread.so.0, librt.so.0, libdl.so.0 即可。

linux下动态库.so安装

/etc/ld.so.conf.d/libc.conf
安装了该共享库, 但执行需要调用该共享库的程序的时候, 程序按照默认共享库路径找不到该共享库文件.

所以安装共享库后要注意共享库路径设置问题, 如下:

1) 如果共享库文件安装到了/lib或/usr/lib目录下, 那么需执行一下ldconfig命令

ldconfig命令的用途, 主要是在默认搜寻目录(/lib和/usr/lib)以及动态库配置文件/etc/ld.so.conf内所列的目录下, 搜索出可共享的动态链接库(格式如lib.so), 进而创建出动态装入程序(ld.so)所需的连接和缓存文件. 缓存文件默认为/etc/ld.so.cache, 此文件保存已排好序的动态链接库名字列表.

2) 如果共享库文件安装到了/usr/local/lib(很多开源的共享库都会安装到该目录下)或其它"非/lib或/usr/lib"目录下, 那么在执行ldconfig命令前, 还要把新共享库目录加入到共享库配置文件/etc/ld.so.conf中, 如下:

echo "/usr/local/lib" >> /etc/ld.so.conf.d/libgtc.conf
echo "/usr/local/lib" >> /etc/ld.so.conf
ldconfig

3) 如果共享库文件安装到了其它"非/lib或/usr/lib" 目录下, 但是又不想在/etc/ld.so.conf中加路径(或者是没有权限加路径). 那可以export一个全局变量LD_LIBRARY_PATH, 然后运行程序的时候就会去这个目录中找共享库.
export LD_LIBRARY_PATH=".":"./lib":$LD_LIBRARY_PATH

LD_LIBRARY_PATH的意思是告诉loader在哪些目录中可以找到共享库. 可以设置多个搜索目录, 这些目录之间用冒号分隔开. 比如安装了一个mysql到/usr/local/mysql目录下, 其中有一大堆库文件在/usr/local/mysql/lib下面, 则可以在.bashrc或.bash_profile或shell里加入以下语句即可:

一般来讲这只是一种临时的解决方案, 在没有权限或临时需要的时候使用.

编译时头文件搜索路径

-Idir

在你是用 #include "file"的时候,gcc/g++会先在当前目录查找你所制定的头文件,如果没有找到,他回到缺省的头文件目录找,如果使用-I 指定了目录,他

回先在你所制定的目录查找,然后再按常规的顺序去找.

对于#include;,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺省的头文件目录查找

-I-

就是取消前一个参数的功能,所以一般在-Idir之后使用

-idirafter dir

在-I的目录里面查找失败,讲到这个目录里面查找.

-Dmacro 相当于C语言中的#define macro
-Dmacro=defn 相当于C语言中的#define macro=defn
-Umacro 相当于C语言中的#undef macro
1) -o 指定输出的文件,如果是一个文件的C程序不指定-o选项,则输出的文件是一个a.out的可执行文件,运行./a.out可以运行,注意LINUX下当前目录默认不在搜索路径下,所以运行当前目录的程序也必须加./ 。
2) -c 只编译不连接,这样生成的程序如果不指定-o是file.o形式的文件,基本名字和C 程序的相同。有些程序可能包括很多个文件,需要将每个文件先编译成.o的文件,然后再连接在一起。
3) -I 指定include搜索的路径。如-I/home/lxq/include即指定include搜索路径中加上/home/lxq/include。

4) -L 指定库的搜索路径。如-L/home/lxq/libs即指定库的搜索路径中加上/home/lxq/libs。
5) -l 指定所用到的库。Linux下的库文件一般是这种格式:静态库libxxxx.a,动态库libxxx.so,这样如果用到了某个库只需在编译的时候加上-lxxx即可。比如-lm,即包含数学库。

6) -g 加入调试信息。加了这个选项之后生成的程序可以进行调试,但是可能程序的执行速度会受到影响。可能用-gdb3来代替,-gdb3加入的调试信息最多。
7) -On 优化生成的目标代码。n是一个具体的数 0-4 ,如3。这样生成的程序没有调试信息 加入不能进行调试,但是程序的效率比较高,对有些程序的执行速度可能会影响很大。

1. 生成动态共享库lib.so 并引用 libm.so

gcc -fPIC -shared libm.so main.c -ldl -o libxx.so
-fPIC:【生成位置无关的代码】一定要大写 使用-fpic 生成的体积小,但可能存在硬件兼容性问题。

2. 生成静态库lib.a

gcc -c *.cpp -c 只编译不链接

ar -rcs libfoo.a foo.o bridge.o
ar -t libfoo.a 查看包对内容
ar -x libfoo.a 解包内容

-a: 与r或m共同使用来将files指明的文件放置于posname之后
-c:create 创建一个库。不管库是否存在,都将创建。
-r:replace 在库中插入模块(替换)。当插入的模块名已经在库中存在,则替换同名的模块。如果若干模块中有一个模块在库中不存在,ar显示一个错误消息,并不替换其他同名模块。默认的情况下,新的成员增加在库的结尾处,可以使用其他任选项来改变增加的位置。
-s:强制重新生成文件包的符号表,写入一个目标文件索引到库中,或者更新一个存在的目标文件索引。甚至对于没有任何变化的库也作该动作。对一个库做ar s等同于对该库做ranlib。

3. 编译共享库lib.so

gcc -shared -fPIC -o 1.so 1.c

-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code),
则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意
位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。

-fPIC参数详解

这里有一个-fPIC参数 PIC就是position independent code
PIC使.so文件的代码段变为真正意义上的共享

如果不加-fPIC,则加载.so文件的代码段时,代码段引用的数据对象需要重定位, 重定位会修改代码段的内容,这就造成每个使用这个.so文件代码段的进程在内核里都会生成这个.so文件代码段的copy.每个copy都不一样,取决于这个.so文件代码段和数据段内存映射的位置.

不加fPIC编译出来的so,是要再加载时根据加载到的位置再次重定位的.(因为它里面的代码并不是位置无关代码)
如果被多个应用程序共同使用,那么它们必须每个程序维护一份so的代码副本了.(因为so被每个程序加载的位置都不同,显然这些重定位后的代码也不同,当然不能共享)
我们总是用fPIC来生成so,也从来不用fPIC来生成a.
fPIC与动态链接可以说基本没有关系,libc.so一样可以不用fPIC编译,只是这样的so必须要在加载到用户程序的地址空间时重定向所有表目.

因此,不用fPIC编译so并不总是不好.
如果你满足以下4个需求/条件:
1.该库可能需要经常更新
2.该库需要非常高的效率(尤其是有很多全局量的使用时)
3.该库并不很大.
4.该库基本不需要被多个应用程序共享

如果用没有加这个参数的编译后的共享库,也可以使用的话,可能是两个原因:
1:gcc默认开启-fPIC选项
2:loader使你的代码位置无关

从GCC来看,shared应该是包含fPIC选项的,但似乎不是所以系统都支持,所以最好显式加上fPIC选项。参见如下

`-shared'

 Produce a shared object which canthen be linked with other
 objects to form an executable.  Not all systems support this
 option.  For predictable results, you must also specify thesame
 set of options that were used togenerate code (`-fpic', `-fPIC',
 or model suboptions) when youspecify this option.(1)

-fPIC 的使用,会生成 PIC 代码,.so 要求为 PIC,以达到动态链接的目的,否则,无法实现动态链接。

non-PIC 与 PIC 代码的区别主要在于access global data, jump label 的不同。
比如一条 access global data 的指令,
non-PIC 的形势是:ld r3, var1
PIC 的形式则是:ld r3, var1-offset@GOT,意思是从 GOT 表的 index 为 var1-offset 的地方处
指示的地址处装载一个值,即var1-offset@GOT处的4个 byte 其实就是 var1 的地址。这个地址只有在运行的时候才知道,是由 dynamic-loader(ld-linux.so) 填进去的。

再比如 jump label 指令
non-PIC 的形势是:jump printf ,意思是调用printf。
PIC 的形式则是:jump printf-offset@GOT,
意思是跳到 GOT 表的 index 为printf-offset 的地方处指示的地址去执行,
这个地址处的代码摆放在 .plt section,
每个外部函数对应一段这样的代码,其功能是呼叫dynamic-loader(ld-linux.so) 来查找函数的地址(本例中是 printf),然后将其地址写到 GOT 表的 index 为printf-offset 的地方,
同时执行这个函数。这样,第2次呼叫 printf 的时候,就会直接跳到 printf 的地址,而不必再查找了。

GOT 是 data section, 是一个 table, 除专用的几个 entry,每个 entry 的内容可以再执行的时候修改;
PLT 是 text section, 是一段一段的 code,执行中不需要修改。
每个 target 实现 PIC 的机制不同,但大同小异。比如 MIPS 没有 .plt, 而是叫.stub,功能和 .plt 一样。

可见,动态链接执行很复杂,比静态链接执行时间长;但是,极大的节省了 size,PIC 和动态链接技术是计算机发展史上非常重要的一个里程碑。

gcc manul上面有说
-fpic If the GOT size for the linkedexecutable exceeds a machine-specific maximum size, you get an error messagefrom the linker indicating that -fpic does not work; in that case, recompilewith -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k andRS/6000. The 386 has no such limit.)

-fPIC If supported for the targetmachine, emit position-independent code, suitable for dynamic linking andavoiding any limit on the size of the global offset table. This option makes adifference on the m68k, PowerPC and SPARC. Position-independent code requiresspecial support, and therefore works only on certain machines.

关键在于GOT全局偏移量表里面的跳转项大小。
intel处理器应该是统一4字节,没有问题。
powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。

-fpic为了节约内存,在GOT里面预留了“短”长度。
而-fPIC则采用了更大的跳转项。

`