18.Unix/Linux系统的C编程
课程名称 | UNIX系统环境与软件开发 |
教学章节 | §11Unix/Linux系统的C编程 |
教学目的 | 掌握UNIX/Linux系统cc、gcc和g++的使用方法,掌握静态库和动态库的构建与使用方法 |
c语言编译器的功能教学重点 | UNIX/Linux系统的文件相关的系统及使用方法 |
教学难点 | UNIX/Linux系统的文件相关的系统及使用方法 |
知识点 | Cc、gcc和g++的使用,文件相关的系统调用 |
教学时间 | |
班级 | |
教学器具 | 课件;多媒体播放系统 |
教学过程 | 11.1 编译器 11.1.1 功能及用法 1.为什么要使用编译器? C语言源程序需要经过编译和链接这两个过程才能转换成二进制可执行程序。 一般在Unix系统中使用的C编译器是cc(C Compiler的缩写)。在各个Linux发行版本中广泛使用的C编译器名为gcc(GNU cc)。 2.功能 gcc能将C/C++源程序和目标程序编译并调用链接程序ld生成可执行文件,如果用户没有给出可执行文件的名字,gcc将默认生成一个名为a.out的可执行文件。 3.用法 gcc的一般用法为: gcc [options] <filenames> 其常用格式为: gcc [-c][-S][-E][-s][-g][-static][-shared][-rdynamic] [-Idir …][-Ldir …][-lmylib][-x Language][-Olevel] [-Dmacro[=defn] …][-Umacro][-mmachine-option …] [-o out_file] infile … 几点说明:gcc根据源程序的后缀名来决定使用哪一种语言的编译器进行编译工作。后缀名为“.c”(小写)的文件被gcc认为是C语言的源程序文件。例如:gcc hello.c。gcc编译出来的可执行程序默认是a.out。g++是一个C++版本的gcc编译器。g++要求C++语言源程序文件带有后缀名“.cc”。例如:g++ 11.1.2 参数及说明 -c 编译后仅输出*.o型的目标文件,而不连接生成可执行程序 -S 编译后仅生成汇编语言文件*.s,但不生成目标文件和可执行代码 -s 生成可执行文件时,删除符号表和重定位信息。生成成品软件时使用 -E 在预处理过程后结束,不进行编译和连接,也不生成可执行代码 -g 在可执行文件中加入调试信息,便于程序的调试 -Idir 将目录dir添加到头文件搜索范围 -lmylib 连接时搜索库libmylib.a -Ldir 将目录dir添加到库文件搜索范围 -o outfile 指定输出文件名。若不指定则采用默认方式 -mcpu=cpu-type 生成与机器相关的汇编代码 -O[L] 编译时进行优化。L为优化级别,分别0~3和s。生成最终产品时使用 -static 禁止使用共享库(动态连接库) -shared 生成共享库 -rdynamic 连接时使用共享库 -Dname[=val] 宏定义变量name[=val] -Uname 取消宏定义变量name -x language 指定前端语言 11.1.3 示例 C程序:设有一个文件名为hello.c的程序,其内容为: #include <stdio.h> main() { printf(“Hello World!\n”); } 执行步骤 gcc hello.c #生成可执行程序a.out gcc –o hello hello.c #生成可执行程序hello gcc –c hello.c #生成目标文件hello.o gcc –S hello.c #生成汇编语言程序hello.s gcc –S –mcpu=i386 #生成80x86格式汇编语言 编译生成可执行文件之后,就可以运行了,方法为: ./a.out 或 ./hello 输出结果 Hello World! 2. c++程序 下面是一个c++版的Hello World程序,其文件名为hello.C,内容为: #include <iostream.h> main (void) { cout << "Hello, World!" << endl; } 编译方法: 使用c++或g++来编译: g++ hello.C #生成可执行程序a.out c++ –o hello hell.C #生成可执行程序hello g++ –s -o Hello hello.C #生成删除符号表的可执行程序Hello 或使用gcc并指定库文件来编译c++程序: gcc –c hello.C #生成目标文件hello.o gcc –o h hello.C –lstdc++#指定标准c++库,生成可执行程序h 11.1.4 gcc的工作过程 使用gcc/g++由C源代码文件生成可执行文件的过程,有以下四个阶段∶ 预处理(也称预编译,Preprocessing);编译(Compilation);汇编(Assembly);链接(Linking)。 11.2 头文件 在标准C中有两种形式的头文件使用方式: #include <headfile.h> #include “headfile.h” 区别: #include <headfile.h>型头文件搜索范围为默认位置/usr/include,#include“headfile.h”型头文件的搜索位置为当前目录,在Linux的GNU C中,若当前目标不存在headerfile.h,则也会到默认位置去搜索。 11.3 链接器与库文件 UNIX/Linux的链接器为ld,其功能是将目标文件或库文件链接在一起,生成可执行文件,一般在编译过程的最后执行。 Linux标准库文件一般存放在目录/lib或/usr/lib。默认情况下链接器查C语言的标准库函数。如果使用的不是标准的库函数,必须通过-llib或-Llibdir告诉链接器ld,否则将无法到库函数。 库文件命名必须遵守一定命名规则,库文件名字必须永远以lib开头,后紧跟库类名,文件名的后缀为 .a:传统静态库 .so:共享库或动态链接库 例如,libc.a为标准C库,libm.a为数学运算静态库,libm.so.6和libc.so.6分别为标准C和数学运算共享库。 11.4 静态库 静态库也叫档案(archive),以.a为后缀,用于编译链接后生成静态可执行文件。用户可以使用库管理程序ar和ranlib来创建和管理自己的或已有的静态库。 11.4.1 引例 设有C语言文件f1.c,f2.c,f3.c,它们的内容分别为: //文件f1.c的内容 f1(int arg) { printf(”F1: you passed: %d\n”,arg); } //文件f2.c的内容: f2(char *arg) { printf(”F2: you passed: %s\n”,arg); } //文件f3.c的内容: #include <stdio.h> main() { fprintf(stderr,”Begine:\n”); f1(15); f2(”Hello World!”); fprintf(stderr,”:End\n”); exit(0); } 可以采用各模块文件分别编译然后再统一链接的办法进行编译。 cc –c f1.c f2.c //生成f1.o和f2.o cc –o f f3.c f1.o f2.o //生成f cc –o fp f3.c f1.c f2.c //生成fp 11.4.2 构造和管理静态库 用户可以使用命令ar构造自己的静态库: cc –c f1.c f2.c // 生成目标文件f1.o 和f2.o ar crv libmyl.a f1.o f2.o // 生成库libmyl.a ranlib libmyl.a // 为子函数建立索引表 说明 ar用于静态库文件的管理,其功能是库创建、修改和从库中取出模块等 ranlib用于为刚建立的库文件建立索引表,通过索引表可以加快库文件搜索速度。其用法为: ranlib [-vV] ar_file ar参数:功能参数:d:从库文件中删除模块;m:移动模块位置,与a、b和i配合使用;p:显示模块的内容到标准输出:q:快速追加模块到库文件的尾部,不检查是否有重复模块;r:在库文件中插入模块,插入过程中替换重名模块;t:列库文件中模块的列表;x:从库文件中取出模块;修饰参数:a:在指定模块后追加;b,i:在指定模块前插入;c:创建库文件;s:为库文件建立或更新索引,例如ar -s libfile与ranlib libfile同;v:显示工作过程信息。 11.4.3 使用自己的库 当用户创建自己的静态库之后,就可以按照使用系统库的方法来使用它。例如: cc –o fp f3.c libmyl.a #使用库libmyl.a和f3.c生成可执行程序fp cc –o fp f3.o libmyl.a #使用库libmyl.a和f3.o生成可执行程序fp cc –o fp f3.c -L. –lmyl #-L指定当前目录,-lmyl指定静态库文件libmyl.a 11.5 共享库 Linux系统的另一种库文件为共享库,用于生成动态链接的可执行程序。 共享库文件名的格式为: libNAME.so.N NAME 为库名,N 为版本号。 可用命令ldd 和ldconfig 命令管理共享库。 11.5.1 构造共享库 Linux的共享库用于生成动态链接的可执行程序。 共享库文件名的格式为:libNAME.so.N。NAME为库名,N为版本号。 常用命令ldd和ldconfig命令管理共享库。 11.5.1 构造共享库 共享库构造非常简单,只需要在构造库的时候使用-shared参数就可以了。 例如:用11.4.1引例中的f1.c和f2.c构造共享库,方法是: cc -c f1.c f2.c //生成目标文件 cc -shared -o libmy.so f1.o f2.o //由目标文件生成共享库 或 cc -shared –o libmy.so –c f1.c f2.c //由源文件生成共享库 11.5.2 共享库的使用 共享库要使用头文件dlfcn.h和几个相关的函数:dlerror,dlopen,dlsym和dlclose。 1. dlopen dlopen用于打开指定共享库,并返回文件描述符。 其原型为: void *dlopen(const char *filename, int flag); dlopen调用失败时返回NULL值,否则返回文件描述符。 Dlopen函数的相关说明 变量filename为共享库名。若文件名不以/开头,则为非绝对路径名,将按以下顺序搜索库文件:(1)环境变量中的LD_LIBRARY_PATH值指定的路径;(2)动态链接缓冲文件/etc/ld.so.cache;(3)库文件默认目录/lib,/usr/lib。 Dlopen函数的相关说明 变量flag用来表示在什么时候解决未定义的符号,其取值范围与意义如下 (1)RTLD_LAZY:指定在动态链接库的函数执行时解决; (2)RTLD_NOW:指定在dlopen返回前就解决所有未定义的符号问题。一旦有未解决好未定义的符号,dlopen将返回NULL表示错误。 注意:RTLD_LAZY和RTLD_NOW可以与RTLD_GLOBAL配合使用,使得那些在以后才加载的库可以获得其中的符号。 2. dlsym dlsym用于返回共享中指定函数的入口地址 其原型为: void *dlsym(void *handle, char *symbol); dlsym根据共享库文件描述符(handle)与符号(symbol),返回symbol对应的(函数)入口地址,相当于返回一个(函数)指针。 3. dlclose dlclose用于关闭已经打开的指定共享库文件,此操作应在共享库相关操作完成之后进行。 其原型为: int dlclose (void *handle); 4. dlerror 函数dlerror()用于返回动态共享库操作状态信息。当共享库操作函数执行失败时,dlerror可以返回出错信息,否则返回值为NULL表示成功。 其原型为: const char *dlerror(void); 5. 共享库使用示例 为了使用刚创建的共享库,需要对11.4.1引例中的模块文件f3.c进行修改。假定修改后的文件被命名为f3n.c,其代码如下: #include <stdio.h> #include <dlfcn.h> #define SO_FILE "./libmy.so" main() { void *sfp; char *err; int tmpi=16; int (*f1)(int ),(*f2)(char *); //定义函数指针 sfp=dlopen(SO_FILE,RTLD_LAZY); //打开共享库 if(sfp==NULL){ fprintf(stderr,dlerror()); exit(1); } f1=dlsym(sfp,"f1"); //获取函数f1入口地址(指针) err=dlerror(); //检查是否成功 if(err){ fprintf(stderr,err); exit(2); } f2=dlsym(sfp,"f2"); //获取函数f2入口地址(指针) err=dlerror(); //检查是否成功 if(err){ fprintf(stderr,err); exit(3); } fprintf(stderr,"-----------begine-------------\n"); f2("Test String"); //调用函数f2 f1(tmpi); //调用函数f1 fprintf(stderr,"++++++++++++end+++++++++++++++\n"); dlclose(sfp); //关闭共享库 exit(0); } 编译方法为: cc –o myp f3n.c –ldl 由共享库libmy.so生成可执行程序myp,-ldl则指示链接程序ld使用dl函数库。 在编译也可以使用-rdynamic参数,告诉链接程序在链接时所有函数均使用共享库。其方法为: cc –rdynamic –o myp f3n.c –ldl 6. 共享库的管理 (1) ldd ldd(Library Dependency Display)用来显示一个可执行程序或共享库所使用的共享库间的依赖关系。例如: #ldd myp; #ldd /usr/bin/mesg (2)ldconfig ldconfig为共享管理程序,其功能是在默认目录(/lib和/usr/lib)或动态库配置文件/etc/f内所列的目录下或指定目录下搜索共享库,进而创建出动态装入程序ld.so所需的链接和缓存文件。 缓存文件默认为/etc/ld.so.cache,其中保存有已排好序的由/etc/f指定的目录内动态链接库名字列表。 为了让系统或用户动态链接库为系统所共享,需要运行ldconfig来对共享库进行配置。 ldconfig通常在系统启动时运行,而当用户安装了一个新的动态链接库时,就需要手工运行这个命令,其用法为: ldconfig [选项 …] ldconfig的使用示例如下: 1) 显示搜索的目录和共享库,并更新缓存文件/etc/ld.so.cache ldconfig -v 默认情况下,ldconfig不输出任何东西。使用-v参数可以显示正在扫描的目录及搜索到的共享库。 2) 安装共享后,更新目录/lib内的符号链接 ldconfig –n /lib 11.6 make与Makefile make命令会根据Makefile的内容对项目进行管理。make能自动确定哪一个模块被修改了,然后再进行统一、无遗漏的编译。 make的用法为: make [-f filename] [options] [targets] make命令的部分参数列表如下: -C dir:在读Makefile或做任何操作前切换目录,一般用于对目录的递归搜索;-d:显示调试信息;-f file:指定file文件作为Makefile,而非使用默认的makefile或Makefile;-I dir:指定Makefile搜索目录;-k:默认情况下make在遇到错误将终止执行,-k可以让在出现错误时工作的尽量长一些,以便观察分析;-n:让make在不真正编译的情况下列出将执行的步骤。 11.6.2 Makefile文件 Makefile文件的内容是描述项目或软件中的模块之间的相互依赖关系以及目标文件、可执行程序产生时要执行的命令等。 (1) Makefile文件包含的5类内容 显式规则:显式告诉make如何编译或构造一个目标; 隐式规则:隐式的通过变量或规则告诉make如何编译或构造一个目标; 变量定义:在Makefile中可以像shell编程一样定义和使用变量; 命令:定义完成某任务所使用的命令,一般为shell命令; 注释:#开始的部分。意义同shell编程。 规则的定义 规则中的项目定义必须从最左边开始,一个规则中的第二行以后的行必须以tab健开始。 规则的格式如下: targets :prerequisites commands 或 targets :prerequisites ;commands commands (2) Makefile文件中的常用符号: $@:目标名;$*:删除了后缀的目标名;$%:当目标是库文件时,目标内的成员名。例如目标x.a(y.o)的目标名为x.a,成员名为y.o;$^:由空格分隔的目标中所有成员;$?:目标中的变化成员;$<:当前目标的第一个相关成员名。 可使用符号“%”定义或重定义模式规则。例如: %.o:%.c cc –c $< 上述语句定义了一个规则:所有目标文件*.o依赖C语言源程序*.c,且生成方法为cc -c $<。 Makefile文件中的符号“=”或“:=” 符号“=”或“:=”用于修改已经定义的变量或在已定义变量的基础上定义新变量。 例如已知:var1 = a.c b.c c.c 则var2 = $(var1:.c=.o) 定义var2 =a.o b.o c.o 而var1 += d.c 重定义var1,其值为var1 = a.c b.c c.c d.c 注意: “:=”与“=”是有区别的。 当使用“=” 时,变量将做递归或扩展 “:=” 只作简单替换。 11.6.3 make的用法简介 Makefile文件内容可包含多个目标,可以通过make obj的方式指定处理的目标,若不指定则默认为第一个。 为了方便的使用Makefile文件对整个项目进行编译,可在Makefile文件内设一个代表整个项目的目标,一般为all。 有时为了对项目进行管理还要设置有clean、install和uninstall目标: clean用于对项目环境进行准备,清除已经生成的目标文件等; install用于对整个项目的成品进行安装; uninstall则是用于对安装的项目进行卸载。 11.6.4 Makefile示例 1. 多模块项目编译示例 在11.4.1引例中有三个模块,它们分别是f1.c,f2.c和f3.c,它们之间的关系如下图。按照gcc的工作过程,对模块的编译和链接过程可分为: (1) 编译各源文件生成目标代码: gcc –c f1.c gcc –c f2.c gcc –c f3.c (2) 生成可执行程序 gcc -o f f1.o f2.o f3.o 上述过程按照Makefile文件的规定可表示为: f: f1.o f2.o f3.o #f依赖于目标文件f1.o,f2.o和f3.o,其生成过程为:gcc -o f f1.o f2.o f3.o f1.o: f1.c #f1.o依赖于f1.c,生成过程为: gcc –c f1.c f2.o: f2.c #f2.o依赖于f2.c,生成过程为: gcc –c f2.c f3.o: f3.c #f3.o依赖于f3.c,生成过程为: gcc –c f3.c 按照Makefile的格式要求,可以构造此项目的Makefile内容为: f: f1.o f2.o f3.o gcc -o f f1.o f2.o f3.o f1.o: f1.c gcc –c f1.c f2.o: f2.c gcc –c f2.c f3.o: f3.c gcc –c f3.c 有了Makefile文件,可以使用make命令对此项目进行编译。编译方法为: make 或 make f 若要编译单个项目,比方说f1.o ,可以使用以下方法: make f1.o 当为Makefile文件增加all、clean和install目标时,Makefile文件可以变为: 2. 多模块项目共享库编译与使用示例 11.5.1共享库创建和11.5.2共享库使用的Makefile文件可分别由so.mk和so.use来实现。 so.mk的内容如下:(参见教材p249) so.use的内容如下:(参见教材pp249-250) 生成目标文件可使用命令 make all –f so.mk 必要时可先运行 make clean -f so.mk 使用共享库编译可使用命令 make all -f so.use 11.7 调试器gdb 11.7.1 gdb功能 监视或修改程序中变量的值 设置断点以使程序在指定的代码行上暂停执行 单步执行或程序跟踪 11.7.2 gdb 基本命令 11.7.3 程序调试方法 下面以11.4.1引例为例,介绍Linux系统内程序调试的基本方法。 1. 编译时使用调试参数-g cc –g –o myp f1.c f2.c f3.c 2. 启动gdb gdb myp 3. 查看源文件 (gdb) list 4. 设置断点 (gdb) break 6 Breakpoint 1 ***** file f3.c, line 6 5. 开始执行 (gdb) run Breakpoint 1, main() at f3.c:6 6 f2("Test String"); 6. 打印变量的值 (gdb) print tmpi $1 = 16 7. 单步执行,跟踪进入函数f1(也使用next命令执行f1,而不进入函数f1) (gdb) step f1 (arg=16) at f1.c:3 3 printf(”F1: you passed %d\n”,arg); 8. 显示f1()变量arg的值 (gdb) print arg $2 arg = 16 9. 可在任何位置使用where命令,显示程序的调用栈,而得到自己位置。方法是: (gdb) where 10. 列当前文件的内容 (gdb) list 11. 程序继续执行直到程序结束 (gdb) continue |
小结 | 掌握掌握UNIX/Linux系统cc、gcc和g++的使用方法;掌握UNIX/Linux系统的文件相关的系统及使用方法 |
作业 | |
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论