3。为什么会出现undefined reference to 'xxxxx'错误?
首先这是链接错误,不是编译错误,也就是说如果只有这个错误,说明你的程序源码本
身没有问题,是你用编译器编译时参数用得不对,你没
有指定链接程序要用到得库,比如你的程序里用到了一些数学函数,那么你就要在编译
参数里指定程序要链接数学库,方法是在编译命令行里加入-lm。
4。-l参数和-L参数
-l参数就是用来指定程序要链接的库,-l参数紧接着就是库名,那么库名跟真正的库文
件名有什么关系呢?
就拿数学库来说,他的库名是m,他的库文件名是libm.so,很容易看出,把库文件名的
头lib和尾.so去掉就是库名了。
好了现在我们知道怎么得到库名了,比如我们自已要用到一个第三方提供的库名字叫lib
test.so,那么我们只要把libtest.so拷贝到/usr/lib
里,编译时加上-ltest参数,我们就能用上libtest.so库了(当然要用libtest.so库里
的函数,我们还需要与libtest.so配套的头文件)。
放在/lib和/usr/lib和/usr/local/lib里的库直接用-l参数就能链接了,但如果库文件
没放在这三个目录里,而是放在其他目录里,这时我们
只用-l参数的话,链接还是会出错,出错信息大概是:“/usr/bin/ld: cannot find
-lxxx”,也就是链接程序ld在那3个目录里找不到
libxxx.so,这时另外一个参数-L就派上用场了,比如常用的X11的库,它放在/usr/X11R
6/lib目录下,我们编译时就要用-L/usr/X11R6/lib -
lX11参数,-L参数跟着的是库文件所在的目录名。再比如我们把libtest.so放在/aaa/bb
b/ccc目录下,那链接参数就是-L/aaa/bbb/ccc -ltest
另外,大部分libxxxx.so只是一个链接,以RH9为例,比如libm.so它链接到/lib/libm.s
o.x,/lib/libm.so.6又链接到/lib/libm-2.3.2.so,
如果没有这样的链接,还是会出错,因为ld只会找libxxxx.so,所以如果你要用到xxxx
库,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一
个链接就可以了ln -s libxxxx-x.x.x.so libxxxx.so
手工来写链接参数总是很麻烦的,还好很多库开发包提供了生成链接参数的程序,名字
一般叫xxxx-config,一般放在/usr/bin目录下,比如
gtk1.2的链接参数生成程序是gtk-config,执行gtk-config --libs就能得到以下输出"-
L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic
-lgmodule -lglib -ldl -lXi -lXext -lX11-lm",这就是编译一个gtk1.2程序所需的g
tk链接参数,xxx-config除了--libs参数外还有一个参
数是--cflags用来生成头文
件包含目录的,也就是-I参数,在下面我们将会讲到。你可以试试执行gtk-config
--libs --cflags,看看输出结果。
现在的问题就是怎样用这些输出结果了,最笨的方法就是复制粘贴或者照抄,聪明的办
法是在编译命令行里加入这个`xxxx-config --libs --
cflags`,比如编译一个gtk程序:gcc gtktest.c `gtk-config --libs--cflags`这样
就差
不多了。注意`不是单引号,而是1键左边那个键。
除了xxx-config以外,现在新的开发包一般都用pkg-config来生成链接参数,使用方法
跟xxx-config类似,但xxx-config是针对特定的开发包
,但pkg-config包含很多开发包的链接参数的生成,用pkg-config --list-all命令可以
列出所支持的所有开发包,pkg-config的用法就是pkg
-config pagName --libs--cflags,其中pagName是包名,是pkg-config--list-all里
列出名单中的一个,比如gtk1.2的名字就是gtk+,pkg-
config gtk+ --libs --cflags的作用跟gtk-config --libs--cflags是一样的。比如:
gcc gtktest.c `pkg-config gtk+ --libs --cflags`
。
5。-include和-I参数
-include用来包含头文件,但一般情况下包含头文件都在源码里用#include xxxxxx实现
,-include参数很少用。-I参数是用来指定头文件目录
,/usr/include目录一般是不用指定的,gcc知道去那里找,但是如果头文件不在/usr/i
nclude里我们就要用-I参数指定了,比如头文件放
在/myinclude目录里,那编译命令行就要加上-I/myinclude参数了,如果不加你会得到
一个"xxxx.h: No such file or directory"的错误。-I
参数可以用相对路径,比如头文件在当前目录,可以用-I.来指定。上面我们提到的--cf
lags参数就是用来生成-I参数的。
6。-O参数
这是一个程序优化参数,一般用-O2就是,用来优化程序用的,比如gcc test.c -O2,优
化得到的程序比没优化的要小,执行速度可能也有所提
高(我没有测试过)。
7。-shared参数
编译动态库时要用到,比如gcc -shared test.c -o libtest.so
8。几个相关的环境变量
PKG_CONFIG_PATH:用来指定pkg-config用到的pc文件的路径,默认是/usr/lib/pkgconf
ig,pc文件是文本文件,扩展名是.pc,里面定义开发
包的安装路径,Libs参数和Cflags参数等等。
CC:用来指定c编译器。
CXX:用来指定cxx编译器。
LIBS:跟上面的--libs作用差不多。
CFLAGS:跟上面的--cflags作用差不多。
CC,CXX,LIBS,CFLAGS手动编译时一般用不上,在做configure时有时用到,一般情况
下不用管。
环境变量设定方法:export ENV_NAME=xxxxxxxxxxxxxxxxx
9。关于交叉编译
交叉编译通俗地讲就是在一种平台上编译出能运行在体系结构不同的另一种平台上,比
如在我们地PC平台(X86 CPU)上编译出能运行在sparc
CPU平台上的程序,编译得到的程序在X86 CPU平台上是不能运行的,必须放到sparc
CPU平台上才能运行。
当然两个平台用的都是linux。
这种方法在异平台移植和嵌入式开发时用得非常普遍。
相对与交叉编译,我们平常做的编译就叫本地编译,也就是在当前平台编译,编译得到
的程序也是在本地执行。
用来编译这种程序的编译器就叫交叉编译器,相对来说,用来做本地编译的就叫本地编
译器,一般用的都是gcc,但这种gcc跟本地的gcc编译器
是不一样的,需要在编译gcc时用特定的configure参数才能得到支持交叉编译的gcc。
为了不跟本地编译器混淆,交叉编译器的名字一般都有前缀,比如sparc-xxxx-linux-gn
u-gcc,sparc-xxxx-linux-gnu-g++ 等等
10。交叉编译器的使用方法
使用方法跟本地的gcc差不多,但有一点特殊的是:必须用-L和-I参数指定编译器用spar
c系统的库和头文件,不能用本地(X86)
的库(头文件有时可以用本地的)。
例子:
sparc-xxxx-linux-gnu-gcc test.c -L/path/to/sparcLib-I/path/to/sparcInclude
在MS-DOS下直接安装 GNU CC 是不可行的,使用 DOS 下的任何
编译器皆无法成功的编译 GNU CC,除非你使用 GNU CC 本身。欲
在 MS-DOS 下安装 GNU CC,你必需取得已完全编译完成的套件:DJGPP。
DJGPP 中含有机器码档案以及原始程式档,并且还包含了所有必需的
编译工具及函式库。
1.如果你是在另外一部机器上,在同一目录下先完成了GNU CC的
编译,执行 "make distclean" 来删除一些可能无用的档案。
其中一个被删除的档案叫做 "Makefile";如果 "make distclean"
回应 Makefile 不存在(not exist)的讯息,那可能表示这个目
录已经被适当的清理完毕。
2.在 System V Release 4 系统上,请确定在路径 "PATH" 设定中,
"/usr/bin" 在 "/usr/ucb" 之前。因为 "/usr/ucb" 中的 cc 使用了
有错误的函式库。
3.你可以执行 "configure" 这支程式来设定 host machine、build machine
、及 target machine。
所谓的 "build machine" 指的是你目前正在使用的机器;而
"host machine" 则是你期望编译後所产生的编译器工作的
机器;最後 "target machine" 则是你期望这个编译器
(正在做编译的那个)产生的目的码的主机。(一般而言,
前两者是相同的)。
如果你正在建立一的产生它自己执行的机器的目的码的编译器
(native compiler),一般而言,不需要在 "configure" 作
任何的设定;它将以目前正在使用的机器作为 host、build 及
target machine。所以当你制作 native compiler 的时候,不需
作任何设定。除非 "configure" 无法指出你的系统组态或是它猜
错了。
这样的情况下,用选项 --build 指定 build machine 的
"configuration name"; host 及 target machine 将预设和 build
machine相同。(若你正在建立的是一个交互编译器,请看
*注解:交互编译器)
底下是一个例子:
./configure --build=sparc-sun-sunos4.1
建置名 (configuration name) 可能是一个标准的形式或者是标准形
式的缩写。
一个完整的建置名有由 "-" 号分开的三个部份。大概是:
"CPU名称-厂商名称-系统名称"(每一个部份可含有自己的
"-" 号,configure 这个程式会自行分辨之)。举例来说:
"m68k-sun-sunos4.1" 是指 Sun 3。
你也可以用别名取代部份的建置名。例如: `sun3' 就代表了 `m68k-sun'
,`sun3-sunos4.1' 也是另一种 Sun 3 的表示方法。你也可以简单点用
`sun3-sunos',因为这边假设 SunOS 的版本预设为 4. `sun3-bsd' 也可
以,因为 `configure' 知道 SunOS 是 Sun 3 上唯一的 BSD 变种。
你可以在各种系统型态後面注明版本号码,及 CPU 型号。在大部份的例子
当中,版本是没用的,而且会被忽略掉。所以如果你知道的话,最好也把
号码加上去。(ranma 注:这段原文怪怪的)
关於所支援的建置名与建置的相关注解,请参考 *注解:建置。在继续安装
GNU CC 之前,你最好看一下那份注解。
底下有四种附加的选项可以让你分别指定不同的硬体与软体建置:
`--with-gnu-as', `with-gnu-ld`, `--with-stabs' 及 `--nfp'。
`--with-gnu-as'
如果你想要将 GNU CC 拿来跟 GNU 组译器 (GNU assembler)
并用的话,你可以在执行 `configure' 时加上 `--with-gnu-as' 这
个选项。
使用这个选项并不会安装 GAS。 它只是将 GNU CC 的输出修改成可以
和 GAS 并用而已。要不要安装 GAS 由你自己决定。
相反地,如果你 *不要* 使用 GAS 而且在建构 GNU CC 时不注明
`--with-gnu-as' 的话,你也要自己决定是不是要安装 GAS。 GNU CC
会在各个目录□找 `as' 这个程式,如果它找到了 GAS 的话,它会用
GAS。如果你不确定 GNU CC 到底是找到哪一个组译器来用的话,在
执行 gcc 时加上 `-v' 这个选项。
会因为你是否使用 GAS 而产生不同变化的系统有:
`hppa1.0-ANY-ANY', `hppa1.1-ANY-ANY', `i386-ANY-sysv',
`i386-ANY-isc',
`i860-ANY-bsd', `m68k-bull-sysv', `m68k-hp-hpux',
`m68k-sony-bsd',
`m68k-altos-sysv', `m68000-hp-hpux', `m68000-att-sysv',
`ANY-lynx-lynxos', 及 `mips-ANY'). 对於其他系统,
`--with-gnu-as' 并不会造成影响。
上述系统中 (除了 HP-PA, 386 上的 ISC, 及 `mips-sgi-irix5.*'),
如果你使用 GAS,你也应该要使用 GNU 连结器 (linker),也就是要
注明 `--with-gnu-ld'。
`--with-gnu-ld'
如果你想要拿 GNU 连结器跟 GNU CC 并用的话,注明这个选项。
这个选项并不会安装 GNU 连结器,它只是改变 GNU CC 的习性来
配合 GNU 连结器而已。比较不同的是,它使得 `collet2' 这个程式
不会被安装。这个程式是在大部份的建构中拿来做系统连结器的前
置处理用的。
`--with-stabs'
在大部份以 MIPS 为基础的系统以及 Alpha 上,你必须指明
要 GNU CC 产生出正规的 ECOFF 除错格式,或是要 BSD 式的
stabs 传递 ECOFF 的符号表。正规的 ECOFF 除错格式并不能完整地
处理 C 以外的语言。而 BSD stabs 格式可以处理其他语言,但只能
在 GNU 除错器 (GNU debugger) 上面做。
在正常情况下,GNU CC 预设使用 ECOFF 除错格式,如果你偏好 BSD
stabs 格式,在编译时注明 `--with-stabs'。
不管你在建立 GNU CC 时是选用哪一种预设值,使用者都可以用
`-gcoff' 和 `-gstabs+' 这两个选项来指定他所要用的除错格式。
`--with-stabs' 在 386 上的 ISC 系统中有特别意义,当 `--with-gas'
也被用到时。它会选择使用嵌在 COFF 输出中的 stabs 除错资讯
(stabs debugging information embedded in COFF output) 。这类的
除错资讯也支援 C++,普通的 COFF 除错资讯并不能做到。
`--with-stabs' 在跑 SVR4 的 386 系统中也有特殊意义。它选择使
用嵌在 ELF 输出中的 stabs 除错资讯。在 386 SVR4 平台上,目前的
C++ 编译器 (2.6.0 版) 并不支援 DWARF 除错资讯。stabs 提供了
另一个可以用的方法。它需要 GAS 和 GDB,因为正常的 SVR4 工具并
不能产生或解译 stabs 格式。
`--nfp'
在某些系统上,你必须藉由这个选项注明这台机器是否有浮点运
算器。这些系统包括了 `m68k-sun-sunosN' 及 `m68k-isi-bsd'。目前
`--nfp' 在其他系统上并没有作用,虽然在别的系统上可能也可以有不
同的效果。
`configure' 这个程式会去原始码目录底下的子目录□面去找那些要跟
GNU CC 整合的其他编译器。例如 GNU 的 C++ 编译器,G++,放在 `cp'
这个子目录□。`configure' 会把建立这些编译器的规则加到 `Makefile'
中。
底下我们会详细说明所有 `configure' 会设定的档案。在正常情况下,
你不需要去担心这些档案。
* 会建立一个叫 `config.h' 的档案,□面有一个 `#include' 记载了你
将来要用来执行这个编译器行的机器的顶层设定档 (*注解:建置)。这个档
案用来定义 host machine 的资讯。其中包含 `tm.h'。
顶层设定档放在 `config' 这个子目录□。它都叫做 `xm-某某东西.h',
通常是 `xm-机器名.h',但有些例外。
如果你的系统不支援符号连结 (symbolic link),你可以要设定
`config.h'
让它包含一条 `#include' 指到适当的档案。
* 会建立一个叫 `tconfig.h' 的档案,它引入了 target machine 的顶层
设定档。这是为了要用来编译一些要在那台机器上跑的程式。
* 会建立一个叫 `tm.h' 的档案,它包含了 target machine 的机器描述
巨集档案 (machine-description macro file)。它应该是放在 `config'
子目录中而且它的名字通常是 `机器名.h'。
* `configure' 这个命令档也会在 `Makefile.in' 这个样版档上加入一些
字
来产生 `Makefile'。 这些额外的文字来自 `config' 目录中叫做
`t-TARGET' 及 `x-HOST' 的档案。如果这些档案不存在,那就表示不需
要为 target 或 host machine 加上其他东西。
4. 安装 GNU CC 的标准目录是 `/usr/local/lib'。如果你打算安装到别的地方,
在执行 `configure' 的注明 `--previx=目录'。`目录' 是你打算用来取代
`/usr/local' 的目录名称,除了底下这个例外: 不管你的编译器安装在何处,
`/usr/local/include' 都是会被加到找寻标头档的目录。如果你不想要这样,
你可以用下面这个 `--local-prefix' 选项。
5. 指定 `--local-prefix=目录',这样可以让你设定编译器找寻标头档的路径。
而不是使用 `/usr/local/include'。
*只有* 在你的电脑上放你特殊的规格档案有不同习惯 (不是 `/usr/local')
时,你才会需要 `--local-prefix' 这个选项。
*不要* 把 `/usr' 指定给 `--local-prefix'!你在 `--local-prefix' 所
用的目录 *必须不* 包含所有的系统标准标头档。如果它真的包含,某些程
式会被编译成错的。(包括 GNU Emacs, 在某些目标机器上),因为这样做会
盖掉或搞丢 `fixincludes' 所定的标头档案集。
6. 确定 Bison 剖析器产生机 (parser generator) 已经安装。(如果 Bison
所产生出来的 `c-parse.c' 跟 `cexp.c' 这两个档案比 `c-parse.y' 跟
`cexp.y' 还新,而且你不打算改这些 `.y' 档的话,这步就不用做。
在 1988 年 9 月 8 号之前的 Bison 版本会产生不正确的 `c-parse.c'。
7. 如果你选择了一个需要其他 GNU 工具(像是 GAS 或 GNU 连结器) 而不是
标准系统工具的组态来做 GNU CC 的话,记得在建立 GNU CC 的目录底下
安装所需的工具,并命名为 `as', 'ld' 或其他相关名称。这样会使得编
译器会在编译 `enquire' 时去找这些适当的工具。
或者是,你可以在做後来的编译工作时把 `PATH' 环境变数值设成 GNU
工具在标准系统工具之前。
8. 建立这个编译器。只要在编译器目录□打入 `make LANGUAGES=c' 就好了。
`LANGUAGES=c' 指明了只会编译 C 编译器。makefile 正常下会去编译所有
它支援的语言,目前是 C, C++ 和 Objective C。然而,只有 C 是你用其
他非 GNU C 编译器所编出来的会动的一个。再说,在这个阶段编译 C 以外
的东西是在浪费时间。
通常,你可以打入这个参数 `LANGUAGES="LIST"' 指明你要的语言,其中
"LIST" 是 `c', `c++' 和 `objective-c' 之一或多个。如果你在 GNU CC
原始码目碌下有其他附加的 GNU 编译器,你会可以加到 `LIST' □面。
忽略掉在编 `insn-emit.c' 时所有 "statement not reached"
的警告讯息,那是正常的。还有,警告讯息 "unknown escape sequence" 在
编 `genopinit.c' 或是其他档案是也是正常的。同理,你也可以不理那些
在 `insn-emit.c' 和 `insn-recog.c' 的 "constant is so large thatit
is unsigned" 和 `enquire.o' □的关於 "comparison always beingzero"
。 其他编译错误可能表示了移植到你的机器或作业系统时的错误,你应该
仔细检查并提出报告 (附注:虫)。
有些要钱的编译器因为它们本身的虫或是限制,在编 GNU CC 时会失败。例如
Microsoft 的编译器说会用光所有巨集空间。有的 Ultrix 的编译器会用完
表示式空间,你必需分开问题发生处的叙述。
9. 如果你在做交互编译器,就此打住。 *附注:交互编译器
10. 用底下这个命令把第一阶段目的档案和可执行档案放到一个目录:
make stage1
这些档案会放到一个叫 `stage1' 的副目录□。当安装完成以後,你或许
会用 `rm -r stage` 把这些档案杀掉。
11. 如果你选择了一个需要其他 GNU 工具(像是 GAS 或 GNU 连结器) 而不是
标准系统工具的组态来做 GNU CC 的话,把这些要用的工具放在 `stage1'
子目录下,并命名为 `as', `ld' 或相关档名。这样会使得在做下一阶段
时第一阶段编译器会在这□找适合的工具。
或者是,你可以在做後续的编译工作时把 `PATH' 环境变数值设成 GNU
工具在标准系统工具之前。
12. 用底下这个命令叫这个编译器自己再重新编译一次:
make CC="stage1/xgcc -Bstage1/" CFLAGS="-g -O2"
这叫做建造第二阶段编译器。
上面这个命令会造出所有支援语言的编译器。如果你不要全部都做,你可以
用 `LANGUAGES="LIST"' 注明你要做的语言。 LIST 可以包含 `c', `c++',
`objective-c' 和 `proto' 之一或多个,以空白分开。`proto' 是指
`protoize' 和 `unprotoize' 这两个程式,它们不是一个独立的语言,
但是你是用 `LANGUAGES' 决定要不要安装它们。
如果你还要继续做第三阶段编译器,那你只要在第二阶段做出 C 语言就好了。
当你做完第二阶段编译器以後,如果磁碟空间快没了,你可以砍掉 `stage1'
这个子目录。
在没有浮点运算硬体的 68000 或 68020 系统上,除非你已经选了一个预设
没有浮点运算器的 `tm.h' 档,不然就用底下这个:
make CC="stage1/xgcc -Bstage1/" CFLAGS="-g -O2-msoft-float"
13. 如果你想以叫这个编译器再编译它自己一次来测试它的话,把其他需要的
GNU 工具 (像是 GAS 或 GNU linker) 放在 `stage2' 子目录□,就像在
你之前在 `stage1' 子目录□做的一样,然後:
make stage2
make CC="stage2/xgcc -Bstage2/" CFLAGS="-g -O2"
这叫做建造第三阶段编译器。 除了 `-B' 选项,编译选项都跟你在做第二
阶段编译器时一样。但是 `LANGUAGES' 这个选项不一定要一样。上面这个
命令会做出所有支援的编译器。如果你不要全部都做,你可以用如前所述的
`LANGUAGES="LIST"' 选项指定你要的语言。
如果你不需要安装任何附加的 GNU 工具,你可能要用底下这个命令
make bootstrap LANGUAGES=LANGUAGE-LISTBOOT_CFLAGS=OPTION-LIST
而不是做 `stage1', `stage2',并执行它们做出来的东西。
14. 然後是比较第二阶段目的档跟最後的目的档 -- 除了时间戳记 (time stamp)
之外,它们应该要一样才对。
在某些系统上,有意义的比较目的档是不可能的;它们总是显示 "不同"。
目前在 Solaris 和一些使用 ELF 目的档格式的系统上都会发生。在某些版本
的 SGI 机器上的 Irix 和 Alpha 系统上的 DEC Unix (OSF/1),你不可能不指
定 `-save-temps' 而比较这些档案。如果你在比较上出了错,去看看上述个别
系统的说明。你在其他系统上也可能发生类似问题。
用这个命令来比较档案:
make compare
它会提到所有第二阶段和第三阶段的目的档的不同。有任何不同的话,不管是
多麽无害,都显示 GNU CC 在第二阶段把编译器做错了,所以有可能有严重
的错误。你应该检查并提出报告。(*附注:虫)
如果你的系统不会在目的档上放时间戳记,有一个比较快的方法来比较它们
(用 Bourne shell):
for file in *.o; do
cmp $file stage2/$file
done
如果你是在 MIPS 的机器上用了 `-mno-mips-tfile' 选项,你将没办法去
比较这些档案。
15. 打 `make install' 安装编译器驱动程式,这□面包括了编译器的各个
阶段 (pass) 和执行时期支援。在 `CC', `CFLAG', `LANGUAGES' □用
跟你在做编译时用一样的值。这样做有个必需的理由是因为某些版本的
make □面有虫,会莫名其妙地重新编译档案。假如你指定了一样的变数
值,那些档案会适当地被重编译。
举例来说,假如你已经建立好第二阶段编译器,你可以用底下的命令:
make install CC="stage2/xgcc -Bstage2/" CFLAGS="-g -O"LANGUAGES="LIST"
这样做会把 `cc1',`cpp' 跟 `libgcc.a' 拷贝到
`/usr/local/lib/gcc-lib/TARGET/VERSION' 这个目录□,这个目录是编译器
驱动程式用来找这些档案的地方。TARGET 是你在执行 `configure' 时所指定的
目标机器型态,而 VERSION 是 GNU CC 的版本号码。这种命名方法是为了要
使得不同版本及/或交互编译器可以同时存在。
这样做也会把驱动程式 `xgcc' 放在 `/usr/local/bin/gcc' □面,使得它
在典型的执行搜寻路径□出现。
在某些系统上,这个命令会使得某些档案被重新编译。这通常是 `make' 的
错。你可以忽略掉这个问题,或是使用 GNU Make。
*警告* Sun 的函式库中的 `alloca' 是有错的。要避免掉这个错误,确定
你安装了用 GNU CC 所编译出来的 GNU CC 可执行档。(就是说,第二阶段
或第三阶段的可执行档,而不是第一阶段的) 它们会把 `alloca' 当成是
内建函数,而不会用到函数库中的那一个。
(通常最好是使用第二阶段或第三阶段所产生的 GNU CC 可执行档,因为一
般来说它们在执行上会比其他的编译器快)
16. 如果你要用 C++,你大概也要安装 libg++ 这套东西。它应该在你取得 GNU C
的同一个地方。由於 GNU C 没有另外的 C 执行时期函式库,所以它也不包含
C++ 执行程式库。所有的 I/O 函数、特殊类别函式库等等都包含在 libg++