690 likes | 953 Views
Linux 开发基础. 乾正光. Linux 程序设计基础 —C 环境. Linux 编程概述 文本编辑器 vi Linux 下函数库 GCC 及其使用 调试工具 gdb GNU make 和 makefile. Linux 编程概述. Linux 软件开发一直在 Internet 环境下讲行。这个环境是全球性的,编程人员来自世界各地。只要能够访问 Web 站点,就可以启动一个以 Linux 为基础的软件项目。
E N D
Linux开发基础 乾正光
Linux程序设计基础—C环境 • Linux编程概述 • 文本编辑器vi • Linux下函数库 • GCC及其使用 • 调试工具gdb • GNU make和makefile
Linux编程概述 • Linux软件开发一直在Internet环境下讲行。这个环境是全球性的,编程人员来自世界各地。只要能够访问Web站点,就可以启动一个以Linux为基础的软件项目。 • Linux开发工作经常是在Linux用户决定共同完成一个项目时开始的。当开发工作完成后,该软件就被放到Internet站点上,任何用户都可以访问和下载它。由于这个活跃的开发环境,新的以Linux为基础的软件功能日益强大,而且呈现爆炸式的增长态势。 • 大多数Linux软件是经过自由软件基金会(Free Software Foundation)提供的GNU(GNU 即 GNU’s not UNIX)公开认证授权的,因而通常被称作GNU软件。GNU软件免费提供给用户使用,并被证明是非常可靠和高效的。许多流行的Linux实用程序如C编译器、shell和编辑器都是GNU软件应用程序。
Linux程序需要首先转化为低级机器语言即所谓的二进制代码以后,才能被操作系统执行。Linux程序需要首先转化为低级机器语言即所谓的二进制代码以后,才能被操作系统执行。 • 例如编程时,先用普通的编程语言生成一系列指令,这些指令可被翻译为适当的可执行应用程序的二进制代码。这个翻译过程可由解释器一步步来完成,或者也可以立即由编译器明确地完成。 • shell编程语言如BASH、TCSH、GAWK、Perl、Tcl和Tk都利用自己的解释器。用这些语言编制的程序尽管是应用程序文件,但可以直接运行。编译器则不同,它将生成一个独立的二进制代码文件然后才可以运行。
Linux编程风格 • GNU风格 • Linux 内核编程风格
GNU风格 (1/2) • 函数返回类型说明和函数名分两行放置,函数起始字符和函数开头左花括号放到最左边。 • 尽量不要让两个不同优先级的操作符出现在相同的对齐方式中,应该附加额外的括号使得代码缩进可以表示出嵌套。 • 按照如下方式排版do-while语句: do { }while() • 每个程序都应该以一段简短的说明其功能的注释开头。 • 请为每个函数书写注释,说明函数是做什么的,需要哪些入口参数,参数可能值的含义和用途。如果用了非常见的、非标准的东西,或者可能导致函数不能工作的任何可能的值,应该进行特殊说明。如果存在重要的返回值,也需要说明。 • 不要声明多个变量时跨行,每一行都以一个新的声明开头。
GNU风格 (2/2) • 当一个if中嵌套了另一个if-else时,应用花括号把if-else括起来。 • 要在同一个声明中同时说明结构标识和变量或者结构标识和类型定义(typedef)。先定义变量,再使用。 • 尽量避免在if的条件中进行赋值。 • 请在名字中使用下划线以分割单词,尽量使用小写;把大写字母留给宏和枚举常量,以及根据统一惯例使用的前缀。例如,应该使用类似ignore_space_change_flag的名字;不要使用类似iCantReadThis的名字。 • 用于表明一个命令行选项是否给出的变量应该在选项含义的说明之后,而不是选项字符之后被命名。
Linux 内核编程风格 • Linux内核缩进风格是8个字符。 • Linux内核风格采用K&R标准,将开始的大括号放在一行的最后,而将结束的大括号放在一行的第一位。 • 命名尽量简洁。 • 不应该使用诸如ThisVariableIsATemporaryCounter之类的名字。应该命名为tmp,这样容易书写,也不难理解。 • 命名全局变量,应该用描述性命名方式,例如应该命名“count_active_users()”,而不是“cntusr()”。 • 本地变量应该避免过长。 • 函数最好短小精悍,一般来说不要让函数的参数多于10个,否则应该尝试分解这个过于复杂的函数。 • 通常情况,注释说明代码的功能,而不是其实现原理。避免把注释插到函数体内,而写到函数前面,说明其功能,如果这个函数的确很复杂,其中需要有部分注释,可以写些简短的注释来说明那些重要的部分,但是不能过多。
文本编辑器vi • vi的模式 • vi的进入 • 命令模式 • 插入模式 • 末行模式
vi的模式 • Command Mode (命令模式) • 这是执行vi后的缺省模式 • 此时键盘输入当作命令 • 命令有大小写之区分 • Input Mode (插入模式) • 使用 a、i、o、c、r、s进入插入模式 • 用户输入的任何字符都被vi当做文件内容保存起来,并将其显示在屏幕上 • 按下 ESC 键即可回到 Command Mode
vi的模式 • Last Mode (末行模式) • 在Command Line 按下 : 即可进入该模式 • 用来进行保存文件、打开文档或环境的设定 • 命令有大小写之分
vi的进入和内容输入 • 进入: vi 文件名 • 输入文件内容 (进入插入模式) • 新增 (append) a 从光标所在位置后面开始新增内容 A 从光标所在行最后面的地方开始新增内容。 • 插入 (insert) i 从光标所在位置前面开始插入内容 I 从光标所在行的第一个非空白字符前面开始插入资料。 • 开始 (open) o 在光标所在行下新增一行并进入输入模式。 O 在光标所在行上新增一行并进入输入模式。 (命令模式)
命令模式 • 1、光标的移动 h 左移一个字符 l 右移一个字符j 下移一行 k 上移一行w,W 跳至后一个字的开头(W忽略标点)b,B 跳至前一个字的开头(B忽略标点)e 移动到后一个字的末尾 ^ 至本行第一个非空字符 $ 至行尾 0 至行首H 移动到当前窗口的第一列 M 移动到当前窗口的中间列 L 移动到视窗的最后一列) 光标所在位置到下个句子的第一个字母 ( 光标所在位置到该句子的第一个字母 } 光标所在位置到该段落的最后一个字母 { 光标所在位置到该段落的第一个字母
命令模式 • 1、光标的移动(续) nH 将光标移到屏幕的第 n 行 nL 将光标移到屏幕的倒数第 n 行 CTRL-d 向下半页 CTRL-f 向下一页 CTRL-u 向上半页 CTRL-b 向上一页 n- 减号移动到上一行的第一个非空白字符,前面加上数字可以指定移动到以 上 n 行 n+ 加号移动到下一行的第一个非空白字符,前面加上数字可以指定移动到以下 n 行
命令模式 • 2、删除 x 删除光标所在字符 X 删除光标前面的字符 s 删除光标所在字符,并进入输入模式 S 删除光标所在的行,并进入输入模式 dd 删除光标所在的行 D 从光标位置开始删除到行尾 d与光标移动命令的组合
命令模式 • 3、修改 r 修改光标所在字符,r 后接着要修改的字符。如,rc 可以用字符“ c ”替换光标所指向的当前字符 R 进入替换状态,新增内容会覆盖原先内容,直到按 [ESC] 回到命令模式下为止 cc 修改光标所在行 C 修改从光标位置到该行末尾的内容 c 与光标移动命令的组合
命令模式 • 4、复制和移动 yy 复制当前行到内存缓冲区 nyy 复制 n 行内容到内存缓冲区 y与光标移动的组合 p 将缓冲区的内容粘贴到光标的后面 P 将缓冲区的内容粘贴到光标的前面 另:在末行模式下实现移动 :n1,n2 m n3 : 把 n1到n2 行内容搬到第 n3 行后
命令模式 5、搜索字符串 /pattern 移至下一个包含pattern的行 ?pattern 移至上一个包含pattern的行 / 往下重复查找 ? 往上重复查找 n 在同一方向重复查找 N 在相反方向重复查找 /pattern/+n 移至下一个pattern所在行后的第n行 ?pattern?-n 移至上一个Pattern所在行前的第n行 6、其他 u 撤销前一条命令的结果 *. $ ^ [ ] { } \ / 包含在查找字符串中,要用转义字符(\ )
末行模式 • 1、文件的保存和退出 :w 保存 :q 退出 :w! 强制保存 :q! 强制退出 :wq 保存退出 :wq! 强制保存退出
末行模式 • 2、字符串的替换 :s/str1/str2/ 用字符串 str2 替换行中首次出现的字符串 str1 :s/str1/str2/g 用字符串 str2 替换行中所有出现的字符串 str1 :.,$ s/str1/str2/g 用字符串 str2 替换正文当前行到末尾所有出现的字符串 str1 :1,$ s/str1/str2/g 用字符串 str2 替换正文中所有出现的字符串 str1 :g/str1/s//str2/g 功能同上
末行模式 • 其他: : n 将光标移到第 n 行 编辑多个文件 vi file1 file2 … :n 编辑下一个文件 :e filename 编辑指定文件
Linux下函数库(1/3) • 一个“程序函数库”就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用。程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级。可分为两种类型: • 静态函数库(static libraries):是一个普通的目标文件的集合,一般用“.a”作为文件的后缀。静态函数库和共享函数库相比有很多的缺点,占用内存空间多。但使用ELF格式的静态库函数生成的代码可以比使用共享函数库的程序运行速度上快一些。 • 可以用ar这个程序来创建一个静态函数库文件,或者往一个已经存在地静态函数库文件添加新的目标代码。 例如, 把file1.o和file2.o加入到my_library.a这个函数库文件: ar rcs my_library.a file1.o file2.o 然后运行 ranlib,以给库加入一些索引信息
Linux下函数库(2/3) • 共享函数库(shared libraries):当一个可执行程序在启动的时候被加载的函数。每个共享函数库都有个特殊的名字,称作“soname”。soname名字命名必须以“lib”作为前缀,然后是函数库的名字,然后是“.so”,最后是版本号信息。 • 优点:多进程使用同一函数库;修改函数库不需重新连编。 • 安装一个新版本的函数库的时候,要先将这些函数库文件拷贝到一些特定的目录中,运行ldconfig就可以。ldconfig检查已经存在的库文件,然后创建soname的符号链接到真正的函数库,同时设置/etc/ld.so.cache这个缓冲文件。 例如,创建两个目标文件(a.o和b.o),然后创建一个包含a.o和b.o的共享函数库。 • gcc -fPIC -g -c -Wall a.cgcc -fPIC -g -c -Wall b.cgcc -shared -Wl,-soname,liblusterstuff.so.1 -o liblusterstuff.so.1.0.1 a.o b.o –lc • 注:” -fPIC ”是位置无关参数, ”-g”和“-Wall”参数不是必须的。
Linux下函数库(3/3) • 函数库和头文件的保存位置 • a. 函数库 • /lib:系统必备共享函数库 • /usr/lib:标准共享函数库和静态函数库 • /usr/i486-linux-libc5/lib:libc5 兼容性函数库 • /usr/X11R6/lib:X11R6 的函数库 • /usr/local/lib:本地函数库 • b. 头文件 • /usr/include:系统头文件 • /usr/local/include:本地头文件 • c. 共享函数库的相关配置和命令 • /etc/ld.so.conf:包含共享库的搜索位置 • ldconfig:共享库管理工具,一般在更新了共享库之后要运行该命令 • ldd:可查看可执行文件所使用的共享函数库
使用GNU cc开发应用程序 • gcc的简介 • 可执行文件的格式 • gcc的使用
源程序(*.c) 预处理 编译 组译 链接 可执行文件 预处理器 编译器 组译器 连接器 gcc的简介 • gcc 是 GNU 的 C 和 C++ 编译器。实际上,gcc 能够编译多种语言:C、C++ 和 Object C等。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。也可以对几个 C 源文件利用 gcc 编译、连接并生成可执行文件。 • gcc可以使程序员灵活地控制编译过程。编译过程一般可以分为下面四个阶段,每个阶段分别调用不同的工具进行处理
gcc的四个阶段 • 命令gcc首先调用cpp进行预处理,在预处理过程中,对源代码文件中的文件包含(include)、预编译语句(如宏定义define等)进行分析。 • 接着调用cc1进行编译,这个阶段根据输入文件生成以.o为后缀的目标文件。 • 汇编过程是针对汇编语言的步骤,调用as进行工作,一般来讲,.S或.s为后缀的汇编语言源代码文件汇编之后都生成以.o为后缀的目标文件。 • 当所有的目标文件都生成之后,gcc就调用ld来完成最后的关键性工作,这个阶段就是连接。在连接阶段,所有的目标文件被安排在可执行程序中的恰当的位置,同时,该程序所调用到的库函数也从各自所在的函数库中连到合适的地方。
可执行文件格式 • Linux系统中可执行文件有两种格式。 • 第一种格式是a.out格式,这种格式用于早期的Linux系统以及 Unix系统的原始格式。a.out来自于Unix C编译程序默认的可执行文件名。当使用共享库时,a.out格式就会发生问题。把a.out格式调整为共享库是一种非常复杂的操作。 • 因此,一种新的文件格式被引入Unix系统5的第四版本和Solaris系统中。它被称为可执行和连接的格式(ELF)。这种格式很容易实现共享库。 • ELF格式已经被Linux系统作为标准的格式采用。 • gcc编译程序产生的所有的二进制文件都是ELF格式的文件(即使可执行文件的默认名仍然是a.out)。较旧的a.out格式的程序仍然可以运行在支持ELF格式的系统上。
GNU C 的使用 • 基本语法 gcc [options] [filenames] • 说明: • 在gcc后面可以有多个编译选项,同时进行多个编译操作。很多的gcc选项包括一个以上的字符。因此你必须为每个选项指定各自的连字符。例如,下面的两个命令是不同的: gcc -p -g test1.c gcc -pg test1.c • 当你不用任何选项编译一个程序时,GCC将会建立(假定编译成功)一个名为a.out的可执行文件。
gcc选项 • -o选项 你能用 -o 编译选项来为将产生的可执行文件指定一个文件名来代替 a.out。 例:gcc –o count count.c • -c选项:告诉GCC仅把源代码编译为目标代码而跳过汇编和连接的步骤。这个选项使用的非常频繁,因为它使得编译多个C程序时速度更快并且更易于管理。缺省时GCC建立的目标代码文件有一个.o的扩展名。 例:gcc –c test2.c • -E 只运行 C 预编译器。 • -S 编译选项告诉 gcc 在为 C 代码产生了汇编语言文件后停止编译。 • -shared 生成共享目标文件。通常用在建立共享库时。 • -static 禁止使用共享连接。
警告选项 • 在gcc中用开关-Wall控制警告信息,使用示例命令如下: gcc –Wall -o test3_1 test3_1.c • -w 不生成任何警告信息。
查找选项 • gcc一般使用默认路径查找头文件和库文件。如果文件所用的头文件或库文件不在缺省目录下,则编译时要指定它们的查找路径。 • -I选项:指定头文件的搜索目录 例: gcc –I/export/home/st –o test1 test1.c • -L选项:指定库文件的搜索目录 例: gcc –L/usr/X11/R6/lib –o test1 test1.c
多个源文件生成一个可执行文件 问题:有多个源文件时,如何生成一个可执行文件? • 方法1:gcc –Wall –o mytest test1.c test2.c test3.c • 方法2: gcc-Wall -c test1.c gcc-Wall –c test2.c gcc-Wall –c test3.c gcc –o mytest test1.o test2.o test3.o
优化选项 • 优化选项可以使GCC在耗费更多编译时间和牺牲易调试性的基础上产生更小更快的可执行文件。这些选项中最典型的是-O和-O2选项。 • -O0 不进行优化处理。 • -O选项:告诉GCC对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。 • -O2选项:告诉GCC 产生尽可能小和尽可能快的代码。-O2选项将使编译的速度比使用-O时慢。但通常产生的代码执行速度会更快。 • -O3选项:比 -O2 更进一步优化,包括 inline 函数。
版本选项 • -v选项 • 用户将会得到自己目前正在使用的gcc的版本及与版本相关的一些信息。 gcc -v 将得到如下结果: Reading specs from /usr/lib/gcc-lib/i486-box-linux/2.7.2/specs gcc version 2.7.2 • -V 选项 • 如果安装了多个版本的gcc,并且想强制执行其中的某个版本,可以用命令通知系统用户要使用的版本。 gcc -V2.6.3 -v
宏定义选项 • -D MACRO 以字符串“1”定义 MACRO 宏。 • -D MACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。 • -U MACRO 取消对 MACRO 宏的定义。
调试和剖析选项 • 使用调试选项后,gcc在进行编译的时候,在目标文件(.o)和创建的可执行文件中插入额外信息,这些额外信息使gdb能够判断编译过的代码和源代码之间的关系。 • -g选项:告诉GCC产生能被 GNU 调试器使用的调试信息以便调试你的程序。 例:gcc –g –o test3 test3.c • -pg选项:告诉GCC在你的程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示你的程序的耗时情况。 • 使用gdb调试工具,命令行如下: 例:gcc –ggdb3 –o test3 test3.c
调试工具gdb • GDB调试器简介 • gdb 的常用命令 • gdb应用实例
gdb 简介 • Linux系统中包含了GNU 调试程序gdb,它是一个用来调试C和 C++ 程序的调试器。可以使程序开发者在程序运行时观察程序的内部结构和内存的使用情况。 • gdb 所提供的一些功能如下所示: • 运行程序,设置所有的能影响程序运行的参数和环境; • 控制程序在指定的条件下停止运行; • 当程序停止时,可以检查程序的状态; • 修改程序的错误,并重新运行程序; • 动态监视程序中变量的值; • 可以单步逐行执行代码,观察程序的运行状态。 • 分析崩溃程序的产生的core文件
gdb的特点 • gdb的功能非常强大 • 到目前为止,gdb已能够支持Moduls-2、Chill、Pascal和FORTRAN程序的调试,但是调试这些语言的源程序时有一些功能还不能使用。 • 例如调试FORTRAN程序时还不支持表达式的输入、输出变量或类FORTRAN的词法。 • gdb程序调试的对象是可执行文件,而不是程序的源代码文件。 • 然而,并不是所有的可执行文件都可以用gdb调试。如果要让产生的可执行文件可以用来调试,需在执行gcc指令编译程序时,加上-g参数,指定程序在编译时包含调试信息。 • 调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。 • gdb 利用这些信息使源代码和机器码相关联。 gdb是一个用来调试C和C++程序的常用调试工具之一。
gdb的启动 • 在命令行上输入gdb并按回车键就可以运行gdb了,如果一切正常的话,将启动gdb gdb [filename] 出现 (gdb) 在这里,可以输入调试命令 • 在可以使用 gdb 调试程序之前,必须使用 -g 选项编译源文件。可在 makefile 中如下定义 CFLAGS 变量: CFLAGS = -g 运行
获取帮助信息 • 启动gdb后,可以在命令行上指定很多的选项。输入: help 可以获得gdb的帮助信息。 • 如果想要了解某个具体命令(比如break)的帮助信息,在gdb提示符下输入下面的命令: break 屏幕上会显示关于break的帮助信息。从返回的信息可知,break是用于设置断点的命令。 • 另一个获得gdb帮助的方法是浏览gdb的手册页。在Linux Shell提示符输入: man gdb 可以看到man的手册页
gdb命令的分类 • 在 gdb 提示符处键入help,将列出命令的分类,主要的分类有: • aliases:命令别名 • breakpoints:断点定义; • data:数据查看; • files:指定并查看文件; • internals:维护命令; • running:程序执行; • stack:调用栈查看; • statu:状态查看; • tracepoints:跟踪程序执行。 • 后跟命令的分类名,可获得该类命令的详细清单
基本gdb命令(1/2) • file命令:装入想要调试的可执行文件。 • cd命令:改变工作目录。 • pwd命令:返回当前工作目录。 • run命令:执行当前被调试的程序。 • kill命令:停止正在调试的应用程序。 • list命令:列出正在调试的应用程序的源代码。 • break命令:设置断点。 • watch命令:设置监视点,监视表达式的变化。 • awatch命令:设置读写监视点。当要监视的表达式被读或写时将应用程序挂起。它的语法与watch命令相同。 • rwatch命令:设置读监视点,当监视表达式被读时将程序挂起,等侍调试。此命令的语法与watch相同。 • next命令:执行下一条源代码,但是不进入函数内部。也就是说,将一条函数调用作为一条语句执行。执行这个命令的前提是已经run,开始了代码的执行。
基本gdb命令(2/2) • step命令:执行下一条源代码,进入函数内部。如果调用了某个函数,会跳到函数所在的代码中等候一步步执行。执行这个命令的前提是已经用run开始执行代码。 • display命令:在应用程序每次停止运行时显示表达式的值。 • info break命令:显示当前断点列表,包括每个断点到达的次数 • 16)info files命令:显示调试文件的信息。 • 17)info func命令:显示所有的函数名。 • 18)info local命令:显示当前函数的所有局部变量的信息。 • 19)info prog命令:显示调试程序的执行状态。 • 20)print命令;显示表达式的值。 • 21)delete命令:删除断点。指定一个断点号码,则删除指定断点。不指定参数则删除所有的断点。 • 22)Shell命令:执行Linux Shell命令。 • 23)make命令:不退出gdb而重新编译生成可执行文件。 • 24)Quit命令:退出gdb。
gdb 使用实例(1/2) • /* 一个有错误的 C 源程序 */ #include <stdio.h> #include <stdlib.h> static char buff [256]; static char* string; int main () { printf ("Please input a string: "); gets (string); printf ("\nYour string is: %s\n", string); } • 上面这个程序非常简单,其目的是接受用户的输入,然后将用户的输入打印出来。该程序使用了 一个未经过初始化的字符串地址 string,因此,编译并运行之后,将出现 Segment Fault 错误: $ gcc -o test -g test.c $ ./test Please input a string: asfd Segmentation fault (core dumped)
gdb 使用实例(2/2) • 为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行: • 1.运行 gdb bugging 命令,装入 bugging 可执行文件; • 2.执行装入的 bugging 命令; • 3.使用 where 命令查看程序出错的地方; • 4.利用 list 命令查看调用 gets 函数附近的代码; • 5.唯一能够导致 gets 函数出错的因素就是变量 string。用 print 命令查看 string 的值; • 6.在 gdb 中,我们可以直接修改变量的值,只要将 string 取一个合法的指针值就可以了,为 此,我们在第 11 行处设置断点; • 7.程序重新运行到第 11 行处停止,这时,我们可以用 set variable 命令修改 string 的取值; • 8.然后继续运行,将看到正确的程序运行结果。
GNU make和makefile • GNU make概述 • Makefile 的基本结构 • Makefile中的变量 • GNU make 的主要预定义变量 • Makefile的隐含规则 • make命令行选项 • 使用automake和autoconf产生Makefile
GNU make概述 • 在大型的开发项目中,人们通常利用 make 工具来自动完成编译工作。这些工作包括: • 如果仅修改了某几个源文件,则只重新编译这几个源文件; • 如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。 • 利用这种自动编译可大大简化开发工作,避免不必要的重新编译。 • 实际上,make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。 当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。 • 默认情况下,GNU make 工具在当前工作目录按如下顺序搜索 makefile: • GNUmakefile • makefile • Makefile