}
则可以如下调用g++命令编译、连接并生成可执行文件:
$g++-ohellohello.C
$./hello
Hello,world!
gcc/egcs的主要选项
选项解释
-ansi只支持ANSI标准的C语法。
这一选项将禁止GNUC的某些特色,
例如asm或typeof关键词。
-c只编译并生成目标文件。
-DMACRO以字符串“1”定义MACRO宏。
-DMACRO=DEFN以字符串“DEFN”定义MACRO宏。
-E只运行C预编译器。
-g生成调试信息。
GNU调试器可利用该信息。
-IDIRECTORY指定额外的头文件搜索路径DIRECTORY。
-LDIRECTORY指定额外的函数库搜索路径DIRECTORY。
-lLIBRARY连接时搜索指定的函数库LIBRARY。
-m486针对486进行代码优化。
-oFILE生成指定的输出文件。
用在生成可执行文件时。
-O0不进行优化处理。
-O或-O1优化生成代码。
-O2进一步优化。
-O3比-O2更进一步优化,包括inline函数。
-shared生成共享目标文件。
通常用在建立共享库时。
-static禁止使用共享连接。
-UMACRO取消对MACRO宏的定义。
-w不生成任何警告信息。
-Wall生成所有警告信息。
SCOUNIX下面dbaxtra的调试技术
在scounix下编程大多离不开C语言,即使是数据库应用也有很多是与c搭配使用的,例如informixesql/c就可以在c语言中嵌入sql语句。
很多人认为在unix下写程序是件很痛苦的事情,其中一个很重要原因是不知道在unix下怎样调试程序。
其实在scounix源码调试器是dbxtra或dbXtra,linux下是gdb。
它们类似turboc的调试器,可以跟踪源码变量。
在unix下调试程序有如下传统方法
一、在要调试语句之前,输出要调试的变量,利用printf()函数。
二、写日志文件,把结果输出到文件中避免屏幕混乱,利用fprintf()函数。
三、利用sco内置调试器dbxtra或dbXtra。
dbxtra适用字符界面,在scounix的图形界面用dbXtra。
(编按:
请注意大小写)
以下是dbxtra基本命令:
ccont在断点后继续执行
ddelete删除所设断点
hhelp帮助
eedit编辑源程序
nnext源程序区的内容向下翻一屏。
pprint显示变量
qquit退出dbxtra
rrun运行程序,直到遇上设置的断点
rrrerun再次运行
sstep单步运行
ststop设置断点
jstatus显示当前断点
twhere显示当前状态,列出所有设置的变量值
didisplay开显示窗,用于查看变量
udundisplay删除显示窗的条目
fforward源程序区的内容向上翻一屏。
Bbackward源程序区的内容向下翻一屏。
Stopistopinst设置断点
traceitraceinst跟踪子程序
dbxtra[options][objectfile]
dbxtra在启动时有个参数-Idir值得一提.我们在编写一个较大程序的时候,通常源程序和编译生成的可执行文件都放在不同的目录中,这样便于管理。
默认dbxtra将在可执行文件所在的目录下找匹配c的源程序。
当我们启动时,指定-I参数,dbxtra就会到我们指定的目录下找匹配的c程序。
例如:
dbxtra-I"\work\c"program1
源程序在用cc编译时要带上-g参数,这样是加上符号表等调试信息。
只有这样编译过的文件,dbxtra才可以调试。
调试信息使源代码和机器码关联。
下面这个C程序输出结果和我们的预想结果不一样,说明某些地方有错误。
我们用调试器来调试它:
程序一:
t.c
main()
{inti=10,*p1;
floatj=1.5,*p2;
p1=&
p2=&
p2=p1;
printf("%d,%d\n",*p1,*p2);
}
首先带上-g参数编译cc-g-ott.c
启动调试器dbxtrat
屏幕显示:
1.main()
2.{inti=10,*p1;
3.floatj=1.5,*p2;
4.p1=&
5.p2=&
6.p2=p1;
7.printf("%d,%d\n",*p1,*p2);
8.}
C[browse]File:
t.cFunc.-
Readubgsymbolicinformation
Type'help'forhelp
(dbxtra)
(dbxtra)
设置断点:
(dbxtra)stopat5
运行:
(dbxtra)run
程序自动在第5行停下。
这时我们可以看变量的值。
(dbxtra)print*p1
单步执行。
(dbxtra)step
程序将执行第5行源码,指针将移到第6行。
(dbxtra)print*p2
(dbxtra)step
程序执行了第6行源码后,将指针移到第7行。
(dbxtra)print*p1,*p2
我们发现在执行了第6行源码后,*p1,*p2的值就不对了,所以问题就出在第6行上。
仔细检查后发现指针p1指向整型,指针p2指向实型。
它们之间的赋值要进行强制类型转换。
这种错误在C程序中是很常见的。
有时我们在调试一些程序时,要在整个程序运行中时刻监视莫些变量的值,例如程序一中我们要时刻了解*p1,*p2的值,除了在每一行程序执行完后,打print*p1,*p2外,还可以开一个显示窗口。
(dbxtra)display*p1,*p2
用undisplay删掉不想要的变量。
有些程序运行时要带参数,mycat/etc/passwd在调试时候
(dbxtra)run'/etc/passwd'
再运行时,无需再写一遍参数。
(dbxtra)rerun
在涉及到curses库编程或屏幕有大量的人机界面时,为了调试方便,我们可以把程序输出结果重定向到个虚屏。
(dbxtra)run>/dev/tty03
当然要先把tty03disable掉。
(disabletty03)
创建和使用静态库
详细的使用情况,请大家man手册,这里只介绍一下。
静态库相对的比较简单。
创建一个静态库是相当简单的。
通常使用ar程序把一些目标文件(.o)组合在一起,成为一个单独的库,然后运行ranlib,以给库加入一些索引信息。
创建和使用共享库
特殊的编译和连接选项
-D_REENTRANT使得预处理器符号_REENTRANT被定义,这个符号激活一些宏特性。
-fPIC选项产生位置独立的代码。
由于库是在运行的时候被调入,因此这个选项是必需的,因为在编译的时候,装入内存的地址还不知道。
如果不使用这个选项,库文件可能不会正确运行。
-shared选项告诉编译器产生共享库代码。
-Wl,-soname-Wl告诉编译器将后面的参数传递到连接器。
而-soname指定了共享库的soname。
#可以把库文件拷贝到/etc/ld.so.conf中列举出的任何目录中,并以
root身份运行ldconfig;或者#运行exportLD_LIBRARY_PATH='pwd',它把当前路径加到库搜索路径中去。
使用高级共享库特性
1.ldd工具
ldd用来显示执行文件需要哪些共享库,共享库装载管理器在哪里找到了需要的共享库.
2.soname
共享库的一个非常重要的,也是非常难的概念是soname——简写共享目标名(shortforsharedobjectname)。
这是一个为共享库(.so)文件而内嵌在控制数据中的名字。
如前面提到的,每一个程序都有一个需要使用的库的清单。
这个清单的内容是一系列库的soname,如同ldd显示的那样,共享库装载器必须找到这个清单。
soname的关键功能是它提供了兼容性的标准。
当要升级系统中的一个库时,并且新库的soname和老的库的soname一样,用旧库连接生成的程序,使用新的库依然能正常运行。
这个特性使得在Linux下,升级使用共享库的程序和定位错误变得十分容易。
在Linux中,应用程序通过使用soname,来指定所希望库的版本。
库作者也可以通过保留或者改变soname来声明,哪些版本是相互兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。
查看/usr/local/lib目录,分析MiniGUI的共享库文件之间的关系
3.共享库装载器
当程序被调用的时候,Linux共享库装载器(也被称为动态连接器)也自动被调用。
它的作用是保证程序所需要的所有适当版本的库都被调入内存。
共享库装载器名字是ld.so或者是ld-linux.so,这取决于Linuxlibc的版本,它必须使用一点外部交互,才能完成自己的工作。
然而它接受在环境变量和配置文件中的配置信息。
文件/etc/ld.so.conf定义了标准系统库的路径。
共享库装载器把它作为搜索路径。
为了改变这个设置,必须以root身份运行ldconfig工具。
这将更新/etc/ls.so.cache文件,这个文件其实是装载器内部使用的文件之一。
可以使用许多环境变量控制共享库装载器的操作(表1-4+)。
表1-4+共享库装载器环境变量
变量含义
LD_AOUT_LIBRARY_PATH除了不使用a.out二进制格式外,与LD_LIBRARY_PATH相同。
LD_AOUT_PRELOAD除了不使用a.out二进制格式外,与LD_PRELOAD相同。
LD_KEEPDIR只适用于a.out库;忽略由它们指定的目录。
LD_LIBRARY_PATH将其他目录加入库搜索路径。
它的内容应该是由冒号
分隔的目录列表,与可执行文件的PATH变量具有相同的格式。
如果调用设置用户ID或者进程ID的程序,该变量被忽略。
LD_NOWARN只适用于a.out库;当改变版本号是,发出警告信息。
LD_PRELOAD首先装入用户定义的库,使得它们有机会覆盖或者重新定义标准库。
使用空格分开多个入口。
对于设置用户ID或者进程ID的程序,
只有被标记过的库才被首先装入。
在/etc/ld.so.perload中指定
了全局版本号,该文件不遵守这个限制。
4.使用dlopen
另外一个强大的库函数是dlopen()。
该函数将打开一个新库,并把它装入内存。
该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。
比如ApacheWeb服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。
一个配置文件控制了加载模块的过程。
这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。
可以在自己的程序中使用dlopen()。
dlopen()在dlfcn.h中定义,并在dl库中实现。
它需要两个参数:
一个文件名和一个标志。
文件名可以是我们学习过的库中的soname。
标志指明是否立刻计算库的依赖性。
如果设置为RTLD_NOW的话,则立刻计算;如果设置的是RTLD_LAZY,则在需要的时候才计算。
另外,可以指定RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。
当库被装入后,可以把dlopen()返回的句柄作为给dlsym()的第一个参数,以获得符号在库中的地址。
使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。
LINUX动态链接库的使用
一、编写合格的动态链接库头文件
C语言的头文件,可供一个或多个程序引用,里面一般定义程序所需的常量,自定义类型及函数原型说明等.其中的函数原型说明,则供编译器检查语法,用于排除引用参数时类型不一致的错误.只有编写合格的动态链接库头文件,程序员才能正确使用动态链接库内的函数.
动态链接库头文件要采用C语言标准格式,其中的动态函数原型定义,不必象上文介绍的那样用(*动态函数名)的描述形式.请看下面的例子每行开始的数字为所在行行号,为笔者添加,供注解使用)
1/*adatetime.h:
纵横软件制作中心雨亦奇(zhsoft@)编写,2002-03-06.*/
2
3#ifndef__DATETIME_H
4
5#define__DATETIME_H
6
7/*日期结构*/
8typedefstruct
9{
10intyear;
11intmon;
12intday;
13}DATETYPE;
14
15/*时间结构*/
16typedefstruct
17{
18charhour;
19charmin;
20charsec;
21}TIMETYPE;
22
23intgetdate(DATETYPE*d);/*取当前日期*/
24intgettime(TIMETYPE*t);/*取当前时间*/
25
26#endif
27
注:
与上文的datetime.h文件比较,从该头文件第23,24行可以看到,动态函数getdate,gettime的原型定义改变了,不再使用(*getdate),(*gettime)的格式了(这种格式使用较为罗嗦).
二、正确编译与命名动态链接库
为了让GCC编译器生成动态链接库,编译时须加选项-shared.(这点须牢记)
LINUX系统中,为了让动态链接库能被系统中其它程序共享,其名字应符合“lib*.so*”这种格式.如果某个动态链接库不符合此格式,则LINUX的动态链接库自动装入程序(ld.so)将搜索不到此链接库,其它程序也无法共享之.
格式中,第一个*通常表示为简写的库名,第二个*通常表示为该库的版本号.如:
在我的系统中,基本C动态链接库的名字为libc.so.6,线程pthread动态链接库的名字为libpthread.so.0等等.本文例子所生成的动态链接库的名字为libmy.so,虽没有版本号,但也符合所要求的格式.
生成该动态链接库的维护文件makefile-lib内容如下:
1#makefile:
纵横软件制作中心雨亦奇编写,2002-03-07.
2
3all:
libmy.so
4
5SRC=getdate.cgettime.c
6
7TGT=$(SRC:
.c=.o)
8
9$(SRC):
adatetime.h
10@touch$@
11
12%.o:
%.c
13cc-c$?
14
15#动态链接库(libmy.so)生成
16libmy.so:
$(TGT)
17cc-s-shared-o$@$(TGT)
18
运行命令:
$m