GCC 使用说明
参数
-fPIC
ppc_85xx-gcc -shared -fPIC liberr.c -o liberr.so
-fPIC 作用于编译阶段,告诉编译器产生与位置无关代码(Position-Independent Code), 则产生的代码中,没有绝对地址,全部使用相对地址,故而代码可以被加载器加载到内存的任意 位置,都可以正确的执行。这正是共享库所要求的,共享库被加载时,在内存的位置不是固定的。
gcc -shared -fPIC -o 1.so 1.c 这里有一个-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 can then be linked with otherobjects to form an executable. Not all systems support thisoption. For predictable results, you must also specify the sameset of options that were used to generate code (`-fpic', `-fPIC',or model suboptions) when you specify 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 linked executable exceeds a machine-specific maximum size, you get an error message from the linker indicating that -fpic does not work; in that case, recompile with -fPIC instead. (These maximums are 8k on the SPARC and 32k on the m68k and RS/6000. The 386 has no such limit.)
-fPIC If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding any limit on the size of the global offset table. This option makes a difference on the m68k, PowerPC and SPARC. Position-independent code requires special support, and therefore works only on certain machines.
关键在于GOT全局偏移量表里面的跳转项大小。 intel处理器应该是统一4字节,没有问题。 powerpc上由于汇编码或者机器码的特殊要求,所以跳转项分为短、长两种。
-fpic为了节约内存,在GOT里面预留了“短”长度。而-fPIC则采用了更大的跳转项。
环境变量
#对所有用户有效修改/etc/profile文件
#对个人有效则修改~/.bashrc文件#在PATH中找到可执行文件程序的路径。
export PATH =$PATH:$HOME/bin (可一次指定多个搜索路径,":"用于分隔它们)#gcc找到头文件的路径
C_INCLUDE_PATH=/usr/include/libxml2:/MyLib
export C_INCLUDE_PATH#g++找到头文件的路径
CPLUS_INCLUDE_PATH=$CPLUS_INCLUDE_PATH:/usr/include/libxml2:/MyLib
export CPLUS_INCLUDE_PATH#找到动态链接库的路径
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/MyLib
export LD_LIBRARY_PATH#找到静态库的路径
LIBRARY_PATH=$LIBRARY_PATH:/MyLib
export LIBRARY_PATH
下面是在gcc命令中手动设置搜索路径:
# 添加头文件搜索路径
gcc foo.c -I /home/xiaowp/include -o foo# 添加动态库搜索路径
gcc foo.c -L /home/xiaowp/lib -lfoo -o foo# 添加静态库搜索路径
gcc foo.c -L /home/xiaowp/lib -static -lfoo -o foo# 库文件安装
sudo apt-cache search 库文件名
sudo apt-get install 下载搜索到的,后缀是-dev的那个包就可以了。
# 或者
# ①进入/usr/local/cppunit/lib,把找不到的动态链接库【libcppunit.so.1.12...】copy到根目录的/lib下。
# ②编辑自己个人目录下的配置文件.bashrc。通过修改LD_LIBRARY_PATH解决。
LD_LIBRARY_PATH=/usr/local/cppunit/lib:LD_LIBRARY_PATH
make
# 这个头文件在你的文件夹里面存在不存在,如果存在,在makefile里面添加路径
# 如果不存在,那你看看那个函数不是系统函数,可以用系统函数代替的就换掉,然后把这个头文件去掉好了#
ubuntu16.04共享库的搜索路径
共享库的搜索是由/lib/ld.so实现的,ld.so首先会在标准路径(/lib和/usr/lib)中查找。需要使用非标准路径中的共享库时,通常将路径加入到/etc/ld.so.conf文件中,并运行sudo ldconfig。/etc/ld.so.conf添加共享库路径举例:
include /etc/ld.so.conf.d/*.conf
/home/admin/testlib
gcc寻找头文件的路径,按照如下的顺序
-
在gcc编译源文件的时候,通过参数-I指定头文件的搜索路径,如果指定路径有多个路径时,则按照指定路径的顺序搜索头文件。命令形式如:“gcc -I /path/where/theheadfile/in sourcefile.c“,这里源文件的路径可以是绝对路径,也可以是相对路径。eg:设当前路径为/root/test,include_test.c如果要包含头文件“include/include_test.h“,有两种方法:
- include_test.c中#include “include/include_test.h”或者#include "/root/test/include/include_test.h",然后gcc include_test.c即可
- include_test.c中#include <include_test.h>或者#include <include_test.h>,然后gcc –I include include_test.c也可
-
通过查找gcc的环境变量C_INCLUDE_PATH/CPLUS_INCLUDE_PATH/OBJC_INCLUDE_PATH来搜索头文件位置。
-
再找内定目录搜索,分别是
-
/usr/include
-
/usr/local/include
-
/usr/lib/gcc-lib/i386-linux/2.95.2/include
-
最后一行是gcc程序的库文件地址,各个用户的系统上可能不一样。
gcc在默认情况下,都会指定到/usr/include文件夹寻找头文件。
gcc还有一个参数:-nostdinc,它使编译器不再系统缺省的头文件目录里面找头文件,一般和-I联合使用,明确限定头文件的位置。在编译驱动模块时,由于非凡的需求必须强制GCC不搜索系统默认路径,也就是不搜索/usr/include要用参数-nostdinc,还要自己用-I参数来指定内核头文件路径,这个时候必须在Makefile中指定。
ERRORS
undefined reference to 'xxxxx'
放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了。但如果库文件没放在这三个目录里,而是放在其他目录里, 这时我们只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find -lxxx”, 也就是链接程序ld在那3个目录里找不到libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库, 它放在/usr/X11R6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -lX11参数,-L参数跟着的是库文件所在的目录名。 再比如我们把libtest.so放在/aaa/bbb/ccc目录下,那链接参数就是-L/aaa/bbb/ccc -ltest
C/C++程序在linux下被编译和连接时,GCC/G++会查找系统默认的include和link的路径,以及自己在编译命令中指定的路径。自己指定的路径就不说了,这里说明一下系统自动搜索的路径。
include头文件路径
除了默认的/usr/include, /usr/local/include等include路径外,还可以通过设置环境变量来添加系统include的路径:
# C
export C_INCLUDE_PATH=XXXX:$C_INCLUDE_PATH
# CPP
export CPLUS_INCLUDE_PATH=XXX:$CPLUS_INCLUDE_PATH
以上修改可以直接命令行输入(一次性),可以在/etc/profile中完成(对所有用户生效),也可以在用户home目录下的.bashrc或.bash_profile中添加(针对某个用户生效),修改完后重新登录即生效。
link链接库文件路径
链接库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的(也可以在编译命令中通过 -l -L 来指定,这里讲的是使用系统默认搜索路径)。 一般 Linux 系统把 /lib /usr/lib /usr/local/lib 作为默认的库搜索路径,所以使用这几个目录中的链接库文件可直接被搜索到(不需要专门指定链接库路径)。对于默认搜索路径之外的库,则需要将其所在路径添加到gcc/g++的搜索路径之中。 链接库文件的搜索路径指定有两种方式:1)修改/etc/so.ld.conf 2)修改环境变量,在其中添加自己的路径
1)在环境变量中添加
# 动态链接库搜索路径:
export LD_LIBRARY_PATH=XXX:$LD_LIBRARY_PATH
# 静态链接库搜索路径:
export LIBRARY_PATH=XXX:$LIBRARY_PATH
以上修改可以直接命令行输入(一次性),可以在/etc/profile中完成(对所有用户生效),也可以在用户home目录下的.bashrc或.bash_profile中添加(针对某个用户生效),修改完后重新登录即生效。
2)在/etc/ld.so.conf 中添加
指定的链接库搜索路径(需要root权限),然后运行 /sbin/ldconfig,以达到刷新 /etc/ld.so.cache的效果。
"undefined reference to" 问题解决方法
最近在Linux下编程发现一个诡异的现象,就是在链接一个静态库的时候总是报错,类似下面这样的错误:
(.text+0x13): undefined reference to `func'
关于undefined reference这样的问题,大家其实经常会遇到,在此,我以详细地示例给出常见错误的各种原因以及解决方法,希望对初学者有所帮助。
链接时缺失了相关目标文件(.o)
测试代码如下:
然后编译。
gcc -c test.c
gcc –c main.c
得到两个 .o 文件,一个是 main.o,一个是 test.o ,然后我们链接 .o 得到可执行程序:
gcc -o main main.o
这时,你会发现,报错了:
main.o: In function `main':
main.c:(.text+0x7): undefined reference to `test'
collect2: ld returned 1 exit status
这就是最典型的undefined reference错误,因为在链接时发现找不到某个函数的实现文件,本例中test.o文件中包含了test()函数的实现,所以如果按下面这种方式链接就没事了。
gcc -o main main.o test.o
【扩展】:其实上面为了让大家更加清楚底层原因,我把编译链接分开了,下面这样编译也会报undefined reference错,其实底层原因与上面是一样的。
gcc -o main main.c //缺少test()的实现文件
需要改成如下形式才能成功,将test()函数的实现文件一起编译。
gcc -o main main.c test.c //ok,没问题了
链接时缺少相关的库文件(.a/.so)
在此,只举个静态库的例子,假设源码如下。
先把test.c编译成静态库(.a)文件
gcc -c test.c
ar -rc test.a test.o
至此,我们得到了test.a文件。我们开始编译main.c
gcc -c main.c
这时,则生成了main.o文件,然后我们再通过如下命令进行链接希望得到可执行程序。
gcc -o main main.o
你会发现,编译器报错了:
/tmp/ccCPA13l.o: In function `main':
main.c:(.text+0x7): undefined reference to `test'
collect2: ld returned 1 exit status
其根本原因也是找不到test()函数的实现文件,由于该test()函数的实现在test.a这个静态库中的,故在链接的时候需要在其后加入test.a这个库,链接命令修改为如下形式即可。
gcc -o main main.o ./test.a //注:./ 是给出了test.a的路径
【扩展】:同样,为了把问题说清楚,上面我们把代码的编译链接分开了,如果希望一次性生成可执行程序,则可以对main.c和test.a执行如下命令。
gcc -o main main.c ./test.a //同样,如果不加test.a也会报错
链接的库文件中又使用了另一个库文件
这种问题比较隐蔽,也是我最近遇到的与网上大家讨论的不同的问题,举例说明如下,首先,还是看看测试代码。
从上图可以看出,main.c调用了test.c的函数,test.c中又调用了fun.c的函数。 首先,我们先对fun.c,test.c,main.c进行编译,生成 .o文件。
gcc -c func.c
gcc -c test.c
gcc -c main.c
然后,将test.c和func.c各自打包成为静态库文件。
ar –rc func.a func.o
ar –rc test.a test.o
这时,我们准备将main.o链接为可执行程序,由于我们的main.c中包含了对test()的调用,因此,应该在链接时将test.a作为我们的库文件,链接命令如下。
gcc -o main main.o test.a
这时,编译器仍然会报错,如下:
test.a(test.o): In function `test':
test.c:(.text+0x13): undefined reference to `func'
collect2: ld returned 1 exit status
就是说,链接的时候,发现我们的test.a调用了func()函数,找不到对应的实现。由此我们发现,原来我们还需要将test.a所引用到的库文件也加进来才能成功链接,因此命令如下。
gcc -o main main.o test.a func.a
ok,这样就可以成功得到最终的程序了。同样,如果我们的库或者程序中引用了第三方库(如pthread.a)则同样在链接的时候需要给出第三方库的路径和库文件,否则就会得到undefined reference的错误。
多个库文件链接顺序问题
这种问题也非常的隐蔽,不仔细研究你可能会感到非常地莫名其妙。我们依然回到第3小节所讨论的问题中,在最后,如果我们把链接的库的顺序换一下,看看会发生什么结果?
gcc -o main main.o func.a test.a
我们会得到如下报错.
test.a(test.o): In function `test':
test.c:(.text+0x13): undefined reference to `func'
collect2: ld returned 1 exit status
因此,我们需要注意,在链接命令中给出所依赖的库时,需要注意库之间的依赖顺序,依赖其他库的库一定要放到被依赖库的前面,这样才能真正避免undefined reference的错误,完成编译链接。
在c++代码中链接c语言的库
如果你的库文件由c代码生成的,则在c++代码中链接库中的函数时,也会碰到undefined reference的问题。下面举例说明。 首先,编写c语言版库文件:
编译,打包为静态库:test.a
gcc -c test.c
ar -rc test.a test.o
至此,我们得到了test.a文件。下面我们开始编写c++文件main.cpp
后编译main.cpp生成可执行程序:
g++ -o main main.cpp test.a
会发现报错:
/tmp/ccJjiCoS.o: In function `main':
main.cpp:(.text+0x7): undefined reference to `test()'
collect2: ld returned 1 exit status
原因就是main.cpp为c++代码,调用了c语言库的函数,因此链接的时候找不到,解决方法:即在main.cpp中,把与c语言库test.a相关的头文件包含添加一个extern "C"的声明即可。例如,修改后的main.cpp如下:
g++ -o main main.cpp test.a
再编译会发现,问题已经成功解决。
invalid use of incomplete type ‘class B’
bob@bob-Latitude-3470:~/dev/workspace/ZSUI_DEV_APK_20161011_ZL1/ezx/ezProject/library/cpp/ezx/sample$ g++ friend_class_demo.cpp
friend_class_demo.cpp: In member function ‘void A::print(B*)’:
friend_class_demo.cpp:15:10: error: invalid use of incomplete type ‘class B’b->mB--;^
friend_class_demo.cpp:4:7: error: forward declaration of ‘class B’class B;
reason
关于类的向前引用声明,声明后,下面的类可以使用它的引用,指针,不可以使用它作为元素,会报错。
class tempAclass tempB {tempA myA; //错误 !!!tempA* right; // 可以的tempA &left; // 可以的
};
因为在这里,仅仅是告诉编译器,这个符号在下面声明了,需要占个位置,编译器不知道具体长度,所以不能声明一个tempA类型的变量,因为无法分配空间。