390 likes | 543 Views
第 3 章 Linux系统C语言开发工具. 本章重点. Linux 环境中将 C 程序转换成可执行文件所经历的过程和命令 gcc 、 make 、 gdb 工具的使用. 2. 3. 1 第一个 Linux C 程序. 例 3- 1 :设计一个程序, 在屏幕打印 5 行“ HELLO, LINUX WORLD” 。 步骤 1: 设计编辑源程序代码 编辑程序只需要用文本编辑器 vim ,编辑程序在终端中输出 : [root@localhost root]#vim 3- 1.c
E N D
本章重点 Linux环境中将C程序转换成可执行文件所经历的过程和命令 gcc、make、gdb工具的使用 2
3.1 第一个Linux C程序 例3-1:设计一个程序,在屏幕打印5行“HELLO, LINUX WORLD”。 步骤 1:设计编辑源程序代码 编辑程序只需要用文本编辑器vim,编辑程序在终端中输出: [root@localhost root]#vim 3-1.c /*3-1.c程序:在屏幕上输出“这是第一个Linux c程序!”*/ #include <stdio.h> /*文件预处理,包含标准输入输出库*/ int main () /*C程序的主函数,开始入口*/ { int i, j; for (i=0,j=5; i < j; i++){ printf("HELLO, LINUX WORLD\n"); exit(0) } return 0; } 输入完成后存盘:按ESC键→输入“:wq”回车 3
3.1 第一个Linux c程序 步骤 2:编译程序 编译程序前,最好请确认c源程序文件的存在,打开Linux终端输入“ls”命令,查看当前目录下是否有3-1.c文件 ; 输入命令:[root@localhost root]#gcc 3-1.c; 若编译时没有出现错误信息,说明程序编译成功。 步骤 3:运行程序 编译好3-1.c程序后,默认生成的可执行文件是a.out; 输入命令:[root@localhost root]#./a.out 此时系统会出现运行结果。 4
3.2.1 gcc编译器 • gcc是(GNU Cmpiler Collection的简称),它是GNU项目中符合ANSI C标准的编译系统,能够编译用C、C++和Object C等语言编写的程序。 • cc 命令可以启动C编译系统,当执行gcc时,它将完成预处理、编译、汇编和连接4个步骤并最终生成可执行代码。产生的可执行程序默认被保存为a.out文件。 • gcc命令可以接受多种文件类型并依据用户指定的命令行参数对它们做出相应处理。 • 如果gcc无法根据一个文件的扩展名决定它的类型,它将假定这个文件是一个目标文件或库文件。
3.2.1 gcc编译器 gcc编译流程 6
3.2.1 gcc编译器 gcc支持编译的一些源文件的后缀及其解释 7
3.2.1 gcc编译器 gcc命令语法: gcc [参数] 要编译的文件 [参数] [目标文件] • 目标文件可省略,gcc默认生成可执行的文件为a.out,如果想要生成自己命名的可执行文件,通常使用“-o”参数。 [root@localhost root]#gcc 3-1.c –o 3-1 8
3.2.1 gcc编译器 • 例3-2:设计一个程序,要求把输入的百分制的成绩转换成五级制输出。即输入成绩大于等于90分,显示“优秀”;若成绩介于80~90分,显示“良好”;若成绩介于70~80分,显示“中等”;若成绩介于60~70分,显示“及格”;若成绩小于60分,显示“不及格”。
3.2.1 gcc编译器 步骤 1:编辑源程序代码 [root@localhost root]#vim 3-2.c 10
3.2.1 gcc编译器 步骤 2:用gcc编译程序 [root@localhost root]#gcc 3-2.c –o 3-2 步骤 3:运行程序 [root@localhost root]#./3-2 思考题:设计一个程序,要求输入一个数,如果此数大于0,显示“输入的为正数”,否则显示“输入的为负数”。 11
3.2.2 函数库 • 标准系统库文件一般存放在linux文件系统/lib和 /usr/lib目录中。C语言编译器需要知道要搜索哪些库文件,默认情况下,它只搜索标准C语言库。 • 库文件必须遵循特定的命名规范并且需要在命令行中明确指定。 • 库文件的名字总是以lib开头,随后的部分指明这是什么库(例如,c代表C语言库,m代表数学库)。文件名的最后部分以.开始,然后给出库文件的类型: .a 代表传统的静态函数库 .so代表共享函数库 例如:libm.a为静态数学函数库。
3.2.2 函数库 1.静态库 • 静态库,也称作归档库(archive),按惯例它们的文件名都以.a结尾。比如,标准C语言函数库/usr/lib/libc.a和X11函数库/usr/X11/lib/libX11.a。 • 创建和维护自己的静态库: • 只要使用 ar(代表archive,即建立归档文件)程序和gcc -c命令对函数分别进行编译。 • 应该尽可能把函数分别保存到不同的源文件中。如果函数需要访问公共数据,则把它们放在同一个源文件中并使用在该文件中声明的静态变量。
3.2.2 函数库 • 例3.5:创建一个小型函数库,它包含两个函数pro1和pro2 ,然后在一个示例程序中调用其中一个函数。 • 步骤1 为两个函数分别创建各自的源文件(将它们分别命名为pro1.c和pro2.c)。 [root@localhost root]# cat pro1.c #include <sdtio.h> void pro1(int arg) { printf(“hello:%d\n”,arg) ; } [root@localhost root]# cat pro2.c #include <sdtio.h> void pro2(char *arg) { printf(“您好:%s\n”,arg) ; }
3.2.2 函数库 • 步骤2 分别编译这两个函数,产生要包含在库文件中的目标文件。通过调用带有-c选项的gcc编译器来实现,-c选项的作用是阻止编译器创建一个完整的程序,gcc将把源程序编译成目标程序,文件名为以.o结尾。如果此时试图创建一个完整的程序将不会成功,因为还未定义main函数。 [root@localhost root]# gcc -c pro1.c pro2.c [root@localhost root]# ls *.o pro1.o pro2.o • 步骤3 编写一个调用pro2函数的程序。首先,为库文件创建一个头文件lib.h。这个头文件将声明库文件中的函数,它应该被所有希望使用库文件的应用程序所包含。 [root@localhost root]# cat lib.h /*lib.h:pro1.c,pro2.c*/ void pro1(int); void pro2(char *);
3.2.2 函数库 • 步骤4 主程序(program.c),它包含库的头文件并且调用库中的一个函数。 [root@localhost root]# cat program.c #include “lib.h” int main() { pro2(“Linux world”); exit(0); } • 步骤5 编译并测试程序。暂时为编译器显式指定目标文件,然后要求编译器编译的文件并将其与预先编译好的目标模块pro2.o链接。 [root@localhost root]# gcc -c program.c [root@localhost root]# gcc -o program program.o pro2.o [root@localhost root]# ./program 您好:Linux world
3.2.2 函数库 • 步骤6 创建并使用一个库文件。用ar程序创建一个归档文件并将目标文件添加进去。这个程序之所以称为ar,是因为它将若干单独的文件归并到一个大的文件中以创建归档文件。注意,也可以用ar程序来创建任何类型文件的归档文件。 [root@localhost root]# ar crv libfoo.a pro1.o pro2.o 函数库就可使用了。
3.2.2 函数库 • 可以在编译器命令行的文件列表中添加该库文件以创建程序: [root@localhost root]# gcc -o program program.o libfoo.a [root@localhost root]# ./program 您好:Linux world • 也可以用-l选项来访问函数库,但是因为其未保存在标准位置,所以必须用-L选项来指示gcc在何处可以找到它: [root@localhost root]#gcc -o program program.o -L. -lfoo • -L.选项指示编译器在当前目录”.”中查找函数库。 • -lfoo选项指示编译器使用名为libfoo.a的函数库(或者名为libfoo.so的共享库,如果它存在的话)。
3.2.2 函数库 2.共享库 • 静态库的一个缺点,当同时运行许多应用程序并且它们都使用来自同一个函数库的函数时,就会在内存中有同一函数的多份拷贝,在程序文件自身中也有多份同样的拷贝。这将消耗大量宝贵的内存和磁盘空间。可以用共享库来实现函数的动态链接。 • Linux支持共享库(动态链接库)。共享库的保存位置与静态库是一样的,但共享库有不同的文件名后缀。在典型的Linux系统中,标准数学库的共享库是/usr/lib/libm.so。 • 程序使用共享库时,它的链接方式是这样的:它本身不再包含函数代码,而是运行时可访问的共享代码。当编译好的程序被装载到内存中执行时,函数引用被解析并产生对共享库的调用,如果有必要共享库才被加载到内存中。
3.3 make工具 • Linux 有个很强大的工具make,它可以管理多个模块。make工具提供灵活的机制来建立大型的软件项目。 • make工具依赖于一个特殊的、名字为makefile或Makefile的文件,这个文件描述了系统中各个模块之间的依赖关系。 • 系统中部分文件改变时,make根据这些关系决定一个需要重新编译的文件的最小集合。如果软件包括几十个源文件和多个可执行文件,这时make工具特别有用。
3.3.1 make命令 • 命令语法: • make [选项] [目标] [宏定义] • 常用选项: -d 显示调试信息 -f 文件 告诉make使用指定文件作为依赖关系文件。 -n 不执行makefile中的命令,只显示输出这些命令 -s 执行但不显示任何信息。
3.3.2 make规则 • makefile的默认文件名为GNUmakefile、makefile或Makefile,当然也可以在make的命令行中指定别的文件名。多数Linux程序员使用第三种文件名Makefile。 • Makefile中包含一些规则来告诉make处理哪些文件以及如何处理这些文件。这些规则主要是描述哪些文件(称为target目标文件,不要和编译时产生的目标文件相混淆)是从哪些别的文件(称为dependency依赖文件)中产生的,以及用什么命令(command)来执行这个过程。 • 依靠这些信息,make会对磁盘上的文件进行检查,如果目标文件的生成或被改动时的时间(称为该文件时间戳)至少比它的一个依赖文件还旧的话,make就执行相应的命令,以更新目标文件。目标文件不一定是最后的可执行文件,可以是任何一个中间文件并可以作为其他目标文件的依赖文件。
3.3.2 make规则 • 一个Makefile文件主要含有一系列的make规则,每条make规则包含以下内容: 目标文件列表:依赖文件列表 <TAB>命令列表 • 目标文件列表:即make最终需要创建的文件,中间用空格隔开,如可执行文件和目标文件;也可以是要执行的动作。 • 依赖文件列表:是编译目标文件所需要的其他文件。 • 命令(command)列表:是make执行的动作,通常是把指定的相关文件编译成目标文件的编译命令,每个命令占一行,且每个命令行的起始字符必须为TAB字符。 • 除非特别指定,否则make的工作目录就是当前目录。
3.3.2 make规则 • 例3.6:Makefile文件实例 [root@localhost root]# cat Makefile # 一个简单的Makefile的例子,以#开头的为注释行 test: prog.o code.o gcc -o test prog.o code.o prog.o: prog.c prog.h code.h gcc -c prog.c -o prog.o code.o: code.c code.h gcc -c code.c -o code.o clean: rm -f *.o
3.3.2 make规则 • 调用make命令: [root@localhost root]# make target • target是Makefile文件中定义的目标之一,如果省略target,make就将生成Makefile文件中定义的第一个目标。 • 对于例3.6,单独的一个“make”命令等价于:[root@localhost root]# make test
3.3.3 Makefile中的变量 • Makefile里的变量就像一个环境变量。事实上,环境变量在make中也被解释成make的变量。这些变量对大小写敏感,一般使用大写宇母。 • Makefile中的变量是用一个字符串在Makefile中定义的,这个字符串就是变量的值。定义变量的语法:VARNAME=string • 引用变量的值: ${VARNAME} • make解释规则时,VARNAME在等式右端展开为定义它的字符串。 • 变量一般都在Makefile的前面部分定义。按照惯例,所有的Makefile变量都应该是大写。如果变量的值发生变化,就只需要在一个地方修改,从而简化了Makefile的维护。
3.3.3 Makefile中的变量 • 利用变量重写前面的Makefile: OBJS=prog.o code.o CC=gcc test: ${ OBJS } ${ CC } -o test ${ OBJS } prog.o: prog.c prog.h code.h ${ CC } -c prog.c -o prog.o code.o: code.c code.h ${ CC } -c code.c -o code.o clean: rm -f *.o
3.3.3 Makefile中的变量 • 环境变量:使用方法很简单,在make启动时,make读取系统当前已定义的环境变量,并且创建与之同名同值的变量,因此用户可以像在shell中一样在Makefile中方便的引用环境变量。需要注意的是,如果用户在Makefile中定义了同名的变量,用户自定义变量将覆盖同名的环境变量。 • 此外,Makefile中还有一些预定义变量和自动变量,但是看起来并不像自定义变量那样直观。
3.4 gdb调试工具 • 上面例子,可以简化为: OBJS=prog.o code.o CC=gcc test: ${ OBJS } ${ CC } -o $@ $^ prog.o: prog.c prog.h code.h code.o: code.c code.h clean: rm -f *.o
3.4 gdb调试工具 • Linux下的gdb调试器,是一款GNU组织开发并发布的UNIX/Linux下的程序调试工具。虽然它没有图形化的友好界面,但是它强大的功能,足以与很多商业化的集成开发工具相媲美。 • 在进行应用程序的调试之前,要注意的是gdb进行调试的是可执行文件,而不是如“.c”这样的源代码文件。因此,需要先通过gcc编译生成可执行文件才能用gdb进行调试。
3.4 gdb调试工具 • 命令语法:gdb [选项][可执行程序[core文件|进程ID]] • 功能:跟踪指定程序的运行,给出它的内部运行状态以协助你定位程序中的错误。你还可以指定一个程序运行错误产生的core文件,或者正在运行的程序进程ID。 • 常用选项: -c core文件 使用指定core文件检查程序。 -h 列出命令行选项的简要介绍。 -n 忽略~/.gdbinit文件中指定的执行命令。 -q 禁止显示介绍信息和版权信息。 -s 文件 使用保存在指定文件中的符号表。
3.4 gdb调试工具 • 启动gdb : 要使用gdb调试程序,必须使用-g参数重新编译该程序。此选项用于生成包含符号表和调试信息的可执行文件。程序成功编译以后,就可以使用gdb调试它,注意gdb产生的(gdb)提示符。 [root@localhost root]# gcc -g hello.c -o hello [root@localhost root]# gdb -q hello …… (gdb) • 启动gdb后,可以使用很多命令。 • 离开gdb :使用quit命令可以离开gdb环境并回到shell提示符。 • gdb支持很多的命令且能实现不同的功能。 • 部分命令:见P94。
3.4 gdb调试工具 • 例3.7 设计一个程序,要求输入两个整数,判断并输出其中的最小数。 • 步骤1 设计编辑源程序代码。 [root@localhost root]#vim 3-7.c
3.4 gdb调试工具 • 步骤2 用gcc编译程序。 • 在编译的时候要加上选项“-g”。这样编译出的可执行代码中才包含调试信息,否则之后gdb无法载入该可执行文件。 [root@localhost root]# gcc 3-7.c -o 3-7 -g • 步骤3 进入gdb调试环境。 • gdb进行调试的是可执行文件,因此要调试的是3-7而不是3-7.c,输入如下: • [root@localhost root]#gdb 3-7 • 回车后就进入了gdb调试模式。
3.4 gdb调试工具 • 步骤4 用gdb调试程序。 (1)查看源文件 • 在gdb中输入“l”(list)就可以查看程序源代码,一次显示10行。 (2)设置断点 • 在gdb中设置断点命令是“b”(break),后面跟行号或者函数名。 (3)查看断点信息 • 用命令“info b”(info break)查看断点信息。 (4)运行程序 • 输入“r”(run)开始运行程序。 (5)查看与设置变量值 • 程序运行到断点处会自动暂停,输入“p变量名”可查看指定变量的值。 (6)单步运行 • 在断点处输入 “n”(next)或者“s”(step)可单步运行。它们之间的区别在于:若有函数,调用时,“s”会进入该函数,而“n”不会进入该函数。
3.4 gdb调试工具 • 步骤4 用gdb调试程序。 (7)继续运行程序 • 在查看完变量或堆栈情况后可以输入“c”(continue)命令恢复程序的正常运行,把剩余的程序执行完,并显示执行结果。 (8)退出gdb环境 • 退出gdb环境只要输入“q”(quit)命令,回车后退出gdb环境。
思考与实验 编写一个简单的C语言程序:输出两行文字“How are you”,在Linux下编辑、编译、运行。 编写一个简单的C语言程序:根据输入的两个整数求平均值并且在终端输出,通过gcc编译器得到它的汇编程序文件。 Makfile文件中的每一行是描述文件间依赖关系的make规则。 对于下面的Makefile文件: CC = gcc OPTIONS = -O3 -o OBJECTS = main.o stack.o misc.o SOURCES = main.c stack.c misc.c HEADERS = main.h stack.h misc.h polish: main.c $(OPJECTS) $(CC) $(OPTIONS) power $(OBJECTS) -lm main.o: main.c main.h misc.h stack.o: stack.c stack.h misc.h misc.o: misc.c misc.h • 回答: • a. 所有变量名字 • b. 所有目标文件的名字 • c. 每个目标的依赖文件 • d. 生成每个目标文件所需执行的命令 • e. 画出makefile对应的依赖关系树。 • f. 生成main.o stack.o和misc.o时会执行哪些命令,为什么? 37
思考与实验 • 用编辑器创建main.c, compute.c, input.c, compute.h, input.h和main.h文件。下面是它们的内容。注意compute.h和input.h文件仅包含了compute和input函数的声明但没有定义。定义部分是在compute.c和input.c文件中。main.c包含的是两条显示给用户的提示信息。
下面程序的功能是提示你输入一个整数并把它显示到屏幕上,现在它能够通过编译但运行不正常。利用gdb找出它的错误并改正它。重新编译和运行改过的程序以确保它工作正常。下面程序的功能是提示你输入一个整数并把它显示到屏幕上,现在它能够通过编译但运行不正常。利用gdb找出它的错误并改正它。重新编译和运行改过的程序以确保它工作正常。 #include<stdio.h> #define PROMPT "请输入一个整数:" void get_input(char *, int *); void main() { int *user_input; get_input(PROMPT, user_input); (void) printf("你输入了:%d。\n", user_input); } void get_input(char *prompt, int *ival) { (void) printf("%s", prompt); scanf("%d", ival); }