1 / 89

Linux 环境下的软件开发

Linux 环境下的软件开发. 北京曙光天演信息技术有限公司. 吕 金. 03年9月18日. 第一章Linux 编程环境简介. 1.0整体思路 本讲是编程系列讲座的第一讲,主要讲述 Linux 平台上的 C 语言环境,包括编译器、 调试器、Make、Diff、Patch 等。其目的是通过介绍 Linux 上 C 语言编程的基本工具 以及一些新手必须掌握的技巧,迅速引导新手入门,以避免走弯路。

derron
Download Presentation

Linux 环境下的软件开发

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Linux 环境下的软件开发 北京曙光天演信息技术有限公司 吕 金 03年9月18日

  2. 第一章Linux 编程环境简介 1.0整体思路 本讲是编程系列讲座的第一讲,主要讲述 Linux 平台上的 C 语言环境,包括编译器、 调试器、Make、Diff、Patch 等。其目的是通过介绍 Linux 上 C 语言编程的基本工具 以及一些新手必须掌握的技巧,迅速引导新手入门,以避免走弯路。 首先对 Linux 作一简单介绍,然后通过讲解 Linux 上进行编程的常识以及典型场景的 演示,使大家对 Linux 上的程序开发有一个感性认识。在此基础上,重点讲述 Linux 上 C 语言编程的基本工具,包括编译器和调试器,主要是它们的重要选项和基本用法。接下来讲述在项目组织和开发过程中非常重要的工具,即 GNU make 和 makefile。 最后讲述 diff/patch 工具。

  3. 1.1 什么是 Linux 1.1.1 Linux 的官方定义 Linux 是一种 UNIX 操作系统的克隆,它(的内核)由 Linus Torvalds 以及网络上组织松散的黑客队伍一起从零开始编写而成。 Linux 的目标是保持和 POSIX 的兼容。 Linux 具备现代一切功能完整的 UNIX 系统所具备的全部特征,其中包括真正的多任务、虚拟内存、共享库、需求装载、共享的写时复制程序执行、优秀的内存管理以及 TCP/IP 网络支持等。 Linux 的发行遵守 GNU 的通用公共许可证。 Linux 起初为基于 386/486 的 PC 机开发,但现在,Linux 也可以运行在 DEC Alpha、SUN Sparc、M68000,以及MIPS 和 PowerPC 等计算机上。

  4. 1.1.3 POSIX 及其重要地位 POSIX 表示可移植操作系统接口(Portable Operating System Interface ,缩写为 POSIX 是为了读音更像 UNIX)。电气和电子工程师协会(Institute of Electrical and Electronics Engineers,IEEE)最初开发 POSIX 标准,是为了提高 UNIX 环境下应用程序的可移植性。然而,POSIX 并不局限于 UNIX。许多其它的操作系统,例如 DEC OpenVMS 和 Microsoft Windows NT,都支持 POSIX 标准,尤其是 IEEE Std. 1003.1-1990(1995 年修订)或 POSIX.1,POSIX.1 提供了源代码级别的 C 语言应用编程接口(API)给操作系统的服务程序,例如读写文件。POSIX.1 已经被国际标准化组织(International Standards Organization,ISO)所接受,被命名为 ISO/IEC 9945-1:1990 标准。 POSIX 现在已经发展成为一个非常庞大的标准族,某些部分正处在开发过程中。表 1-1 给出了 POSIX 标准的几个重要组成部分。POSIX 与 IEEE 1003 和 2003 家族的标准是可互换的。除 1003.1 之外,1003 和 2003 家族也包括在表中。

  5. 1.1.3 POSIX 及其重要地位 1003.0 管理 POSIX 开放式系统环境(OSE)。IEEE 在 1995 年通过了这项标准。 1003.1 被广泛接受、用于源代码级别的可移植性标准。1003.1 提供一个操作系统的 C 语言应用编程接口(API)。IEEE 和 ISO 已经在 1990 年通过了这个标准,IEEE 在 1995 年重新修订了该标准。 1003.1b 一个用于实时编程的标准(以前的 P1003.4 或 POSIX.4)。这个标准在 1993 年被 IEEE 通过,被合并进 ISO/IEC 9945-1。 1003.1c 一个用于线程(在一个程序中当前被执行的代码段)的标准。以前是 P1993.4 或 POSIX.4 的一部分,这个标准已经在 1995 年被 IEEE 通过,归入 ISO/IEC 9945-1:1996。 1003.1g 一个关于协议独立接口的标准,该接口可以使一个应用程序通过网络与另一个应用程序通讯。 1996 年,IEEE 通过了这个标准。 1003.2 一个应用于 shell 和 工具软件的标准,它们分别是操作系统所必须提供的命令处理器和工具程序。 1992 年 IEEE 通过了这个标准。ISO 也已经通过了这个标准(ISO/IEC 9945-2:1993)。 1003.2d 改进的 1003.2 标准。 1003.10应用于超级计算应用环境框架(Application Environment Profile,AEP)的标准。在 1995 年,IEEE 通过了这个标准。 1003.13一个关于应用环境框架的标准,主要针对使用 POSIX 接口的实时应用程序。在 1998 年,IEEE 通过了这个标准。 1003.22一个针对 POSIX 的关于安全性框架的指南。 1003.23一个针对用户组织的指南,主要是为了指导用户开发和使用支持操作需求的开放式系统环境(OSE)框架 2003针对指定和使用是否符合 POSIX 标准的测试方法,有关其定义、一般需求和指导方针的一个标准。在 1997 年,IEEE 通过了这个标准。 2003.1这个标准规定了针对 1003.1 的 POSIX 测试方法的提供商要提供的一些条件。在 1992 年,IEEE 通过了这个标准。 2003.2一个定义了被用来检查与 IEEE 1003.2(shell 和 工具 API)是否符合的测试方法的标准。在 1996 年,IEEE 通过了这个标准。

  6. 1.1.4 GNU 和 Linux 的关系 GNU 是 GNU Is Not UNIX 的递归缩写,是自由软件基金会的一个项目,该项目的目标是开发一个自由的 UNIX 版本,这一 UNIX 版本称为 HURD。尽管 HURD 尚未完成,但 GNU 项目已经开发了许多高质量的编程工具,包括 emacs 编辑器、著名的 GNU C 和 C++ 编译器(gcc 和 g++),这些编译器可以在任何计算机系统上运行。所有的 GNU 软件和派生工作均适用 GNU 通用公共许可证,即 GPL。GPL 允许软件作者拥有软件版权,但授予其他任何人以合法复制、发行和修改软件的权利。 Linux 的开发使用了许多 GNU 工具。Linux 系统上用于实现 POSIX.2 标准的工具几乎都是 GNU 项目开发的,Linux 内核、GNU 工具以及其他一些自由软件组成了人们常说的 Linux: * 符合 POSIX 标准的操作系统 Shell 和外围工具。 * C 语言编译器和其他开发工具及函数库。 * X Window 窗口系统。 * 各种应用软件,包括字处理软件、图象处理软件等。 * 其他各种 Internet 软件,包括 FTP 服务器、WWW 服务器等。 * 关系数据库管理系统等。

  7. 1.2 Linux 的发展历史以及关键人物 1.2.1 UNIX, GNU, Linux 的关键人物及贡献 Ken Thompson, Dennis Ritchie:UNIX;60 年代末 Brian Kernighan, Dennis Ritchie:The C Programming Language;70 年代末 Richard Stallman:FSF,GNU,GPL,emacs,gcc;80 年代中 Andrew S. Tanenbaum:MINIX,Operating Systems: Design and Implementation; 80 年代末,90年代初 Linus Torvalds:Linux;90 年代 Eric Raymond:《黑客文化简史》,《如何成为一名黑客》,《大教堂和市集》,《开拓智域》,《魔法大锅炉》 1.2.2 Linux 发展的重要里程碑 1990, Linus Torvalds 首次接触 MINIX 1991 中, Linus Torvalds 开始在 MINIX 上编写各种驱动程序等操作系统内核组件 1991 底, Linus Torvalds 公开了 Linux 内核 1993, Linux 1.0 版发行,Linux 转向 GPL 版权协议 1994, Linux 的第一个商业发行版 Slackware 问世???? 1996, 美国国家标准技术局的计算机系统实验室确认 Linux 版本 1.2.13符合 POSIX 标准 1999, Linux 的简体中文发行版相继问世 2000 中, LinuxWorld China 2000 展览会召开,涌现大量基于 Linux 的嵌入式系统。

  8. 1.3 Linux 与其他操作系统 1.3.1 Linux 与主要 UNIX 系统之间的关系 UNIX 是七十年代由 AT&&;T 的 Bell 实验室开发的。Bell 实验室继续进行 UNIX 的开发工作,发行了几个版本的 UNIX:System V 第一版(SVR1)、SVR2、SVR3、SVR4 和后来的 System III。 在维护和发展 UNIX 过程中,Bell 实验室将 UNIX 的源代码向教育界公开。许多学校接受了 UNIX 的拷贝,并向操作系统增加了许多新特性,California 大学的 Berkeley 分校就是其中一个。最终,该学校发行了自己的 UNIX 版本,称为 Berkeley Software Distribution(BSD)UNIX。BSD UNIX 使用最广泛的版本是 4.3 和 4.4(称为 4.4BSD)。 到 4.4BSD UNIX 分布时,这个版本中已经很少有最初 Bell 实验室的 UNIX 代码了。不久,几个小组相继编写了新的代码,替换掉 Bell 实验室代码的剩余那部分,使 BSD UNIX 适合在 Intel 386 处理器上运行。这导致了用于 Intel PC 的免费的 BSD UNIX 的 FreeBSD 和 NetBSD 版本的出现。 Linux 不仅符合 POSIX 标准,而且还包括其它 UNIX 标准的多种特性,例如,UNIX 的 System V 接口文档(System V Interface Document,SVID)和伯克利软件发布(Berkeley Software Distribution,BSD)版本。Linux 采用了折衷的策略,包含了 UNIX 几个典型特性当中最实用的一些功能: * Linux 采用了 SVR4 的进程间通信(IPC)机制:共享内存、消息队列、信号灯 * Linux 支持 BSD Socket 网络编程接口 * 许多 Linux 发行版采用 SysV init 机制,支持运行级别

  9. 1.3.2 Linux 与 Windows 操作系统 * 开放与封闭 * 大视野与小圈子 * 创新与跟随

  10. 1.4 Linux 的编程常识 * 标准 (ANSI C, POSIX, SVID, XPG, ...) * 函数库和系统调用 * 在线文档 (man, info, HOW-TO, ...) * C 语言编程风格 * 库和头文件的保存位置 * 共享库及其相关配置

  11. 1.4.1 标准 (ANSI C, POSIX, SVID, XPG, ...) * ANSI C:这一标准是 ANSI(美国国家标准局)于 1989 年制定的 C 语言标准。 后来被 ISO(国际标准化组织)接受为标准,因此也称为 ISO C。 ANSI C 的目标是为各种操作系统上的 C 程序提供可移植性保证,而不仅仅限于 UNIX。 该标准不仅定义了 C 编程语言的语发和语义,而且还定义了一个标准库。这个库可以根据 头文件划分为 15 个部分,其中包括:字符类型 (<ctype.h>)、错误码 (<errno.h>)、 浮点常数 (<float.h>)、数学常数 (<math.h>)、标准定义 (<stddef.h>)、 标准 I/O (<stdio.h>)、工具函数 (<stdlib.h>)、字符串操作 (<string.h>)、 时间和日期 (<time.h>)、可变参数表 (<stdarg.h>)、信号 (<signal.h>)、 非局部跳转 (<setjmp.h>)、本地信息 (<local.h>)、程序断言 (<assert.h>) 等等。 * POSIX:该标准最初由 IEEE 开发的标准族,部分已经被 ISO 接受为国际标准。该标准的具体内容 见 1.1.3。POSIX.1 和 POSIX.2 分别定义了 POSIX 兼容操作系统的 C 语言系统接口 以及 shell 和工具标准。这两个标准是通常提到的标准。 * SVID:System V 的接口描述。System V 接口描述(SVID)是描述 AT&&;T Unix System V 操作 系统的文档,是对 POSIX 标准的扩展超集。 * XPG:X/Open 可移植性指南。X/Open 可移植性指南(由 X/Open Company, Ltd.出版), 是比 POSIX 更为一般的标准。X/Open 拥有 Unix 的版权,而 XPG 则指定成为 Unix 操作系统必须满足的要求。

  12. 1.4.2 函数库和系统调用 1. glibc 众所周知,C 语言并没有为常见的操作,例如输入/输出、内存管理,字符串操作等提供内置的支持。相反,这些功能一般由标准的“函数库”来提供。GNU 的 C 函数库,即 glibc,是 Linux 上最重要的函数库,它定义了 ISO C 标准指定的所有的库函数,以及由 POSIX 或其他 UNIX 操作系统统变种指定的附加特色,还包括有与 GNU 系统相关的扩展。目前,流行的 Linux 系统使用 glibc 2.0 以上的版本。glibc 基于如下标准: * ISO C * POSIX * Berkeley Unix * SVID * XPG

  13. 1.4.2 函数库和系统调用 2. 其他重要函数库 除 glibc 之外,流行的 Linux 发行版中还包含有一些其他的函数库,这些函数库具有重要地位,例如: * GNU Libtool:GNU Libtool 实际是一个脚本生成工具,它可以为软件包开发者提供一般性 的共享库支持。 以前,如果源代码包的开发者要利用共享库的优点,则必须为每个软件包可支持的平台编写 定制的支持代码。并且还需要设计配置接口,以便软件包的安装程序能够正确选择要建立的 库类型。利用 GNU Libtool,则可以简化开发者的这一工作。它在一个单独的脚本中同时封装 了与平台相关的依赖性以及用户界面。GNU Libtool 可使每个宿主类型的完整功能可通过 一般性的接口获得,同时为程序员隐藏了宿主的特殊性。GNU Libtool 一致性接口是可靠的, 用户不必阅读那些晦涩的文档,以便在每个平台上建立共享库。他们只需运行软件包的配置 脚本,而由 libtool 完成繁复的工作。 * CrackLib:CrackLib 为用户提供了一个 C 语言函数接口,利用这一函数,可避免用户选择 容易破解的密码。该函数库可在类似 passwd 的程序中使用。 * LibGTop:LibGTop 是一个能够获取进程信息以及系统运行信息的函数库,这些信息包括: 系统的一般信息、SYS V IPC 限制、进程列表、进程信息、进程映射、文件系统使用信息等。 * 图形文件操作函数库:包括 libungif、libtiff、libpng、Imlib, libjpeg 等,可分别用来操作 GIF、TIFF、PNG、JPEG 以及其他一些格式图形文件。

  14. 1.4.2 函数库和系统调用 3. 系统调用 系统调用是操作系统提供给外部程序的接口。在 C 语言中,操作系统的系统调用通常通过函数调用的形式完成,这是因为这些函数封装了系统调用的细节,将系统调用的入口、参数以及返回值用 C 语言的函数调用过程实现。在 Linux 系统中,系统调用函数定义在 glibc 中。 谈到系统调用时,需要注意如下几点: * 系统调用函数通常在成功时返回 0 值,不成功时返回非零值。如果要检查失败原因,则 要判断 errno 这个全局变量的值,errno 中包含有错误代码。 * 许多系统调用的返回数据通常通过引用参数传递。这时,需要在函数参数中传递一个 缓冲区地址,而返回的数据就保存在该缓冲区中。 * 不能认为系统调用函数就要比其他函数的执行效率高。要注意,系统调用是一个非常耗时 的过程。 有关系统调用我们将在以后详细讲述。

  15. 1.4.3 在线文档 (man, info, HOW-TO...) 1. man man,即 manunal,是 UNIX 系统手册的电子版本。根据习惯,UNIX 系统手册通常分为不同的部分(或小节,即 section),每个小节阐述不同的系统内容。目前的小节划分如下: 1. 命令:普通用户命令 2. 系统调用:内核接口 3. 函数库调用:普通函数库中的函数 4. 特殊文件:/dev 目录中的特殊文件 5. 文件格式和约定:/etc/passwd 等文件的格式 6. 游戏。 7. 杂项和约定:标准文件系统布局、手册页结构等杂项内容 8. 系统管理命令。 9. 内核例程:非标准的手册小节。便于 Linux 内核的开发而包含 常用命令行: $ man open $ man 7 man

  16. 1.4.3 在线文档 (man, info, HOW-TO...) 2. info Linux 中的大多数软件开发工具都是来自自由软件基金会的 GNU 项目,这些工具软件件的在线文档都以 info 文件的形式存在。info 程序是 GNU 的超文本帮助系统。 info 文档一般保存在 /usr/info 目录下,使用 info 命令查看 info 文档。要运行 info,可以在 shell 提示符后输入 info,也可以在 GNU 的 emacs 中键入 Esc-x 后跟 info。 info 帮助系统的初始屏幕显示了一个主题目录,你可以将光标移动到带有 * 的主题菜单上面,然后按回车键进入该主题,也可以键入 m,后跟主题菜单的名称而进入该主题。例如,你可以键入 m,然后再键入 gcc 而进进入 gcc 主题中。 如果你要在主题之间跳转,则必须记住如下的几个命令键: * m: 指定菜单名而选择另外一个节点; * l:进入该窗口中的最后一个节点; * RET:进入光标处的超文本链接; * u:转到上一级主题; * d:回到 info 的初始节点目录; * h:调出 info 教程; * q:退出 info。 #DEMO#

  17. 1.4.3 在线文档 (man, info, HOW-TO...) 3. HOW-TO 可供用户参考的联机文档的另一种形式是 HOWTO 文件,这些文件位于系统的 /usr/share/doc/ 目录下。 HOWTO 文件的文件名都有一个 -HOWTO 后缀,并且都是文本文件。 每一个 HOWTO 文件包含 Linux 某一方面的信息,例如它支持的硬件或如何建立一个引导盘。 http://www.tldp.org 维护Linux 相关HOWTO的当前最新版本。 4. 其他 Linux 的内核文档一般包含在内核源代码中,目录如下:/usr/src/linux-2.x.x/Documentation

  18. 1.4.4 C 语言编程风格 编写这一小节的目的是提醒大家在编程过程中注意编程风格。如果你只是在编写一些小的练习程序,程序只有一两百行长的话,编程风格可能并不重要。然而,如果你和许多人一起进行开发工作,或者,你希望在过一段时间之后,还能够正确理解自己的程序的话,就必须养成良好的编程习惯。在诸多编程习惯当中,编程风格是最重要的一项内容。 良好的编程风格可以在许多方面帮助开发人员。如果你阅读过 Linux 内核源代码的话,可能会对程序的优美编排所倾倒。良好的编程风格可以增加代码的可读性,并帮助你理清头绪。如果程序非常杂乱,大概看一眼就该让你晕头转向了。编程风格最能体现一个程序员的综合素质。 许多读者可能对 Windows 所推崇的匈牙利命名法很熟悉。这种方法定义了非常复杂的函数、变量、类型等的命名方法,典型的命名方法是采用大小写混写的方式,对于变量名称,则采用添加前缀的办法来表示其类型,例如: char szBuffer[20]; int nCount; 利用 sz 和 n 分别代表字符串和整数。为了表示一个变量名称,采用如下的变量名称是可能的: int iThisIsAVeryLongVariable; 在 Linux 中,我们经常看到的是定义非常简单的函数接口和变量名称。在 Linux 内核的源代码中,可以看到 Linux 内核源代码的编码风格说明(/ Documentation/CodingStyle)。UNIX 系统的一个特点是设计精巧,并遵守积木式原则。C 语言最初来自 UNIX 操作系统,与 UNIX 的设计原则一样, C 语言被广泛认可和使用的一个重要原因是它的灵活性以及简洁性。因此,在利用 C 语言编写程序时,始终应当符合其简洁的设计原则,而不应当使用非常复杂的变量命名方法。尽量采用好的编码风格而不是添加过多的注释。

  19. 1.4.4 C 语言编程风格 Linus 为 Linux 内核定义的 C 语言编码风格要点如下: 1. 缩进时,使用长度为 8 个字符宽的 Tab 键。如果程序的缩进超过 3 级,则应考虑重新设计程序。 2. 大括号的位置。除函数的定义体外,应当将左大括号放在行尾,而将右大括号放在行首。函数的定义体应将左右大括号放在行首。如下所示: int function(int x, int y) { if (x == y) { ... } else if (x > y) { ... } else { ... } return 0; } 3. 应采用简洁的命名方法。对变量名,不赞成使用大小写混写的形式,但鼓励使用描述性的名称;尽可能不使用全局变量;不采用匈牙利命名法表示变量的类型;采用短小精悍的名称表示局部变量;保持函数短小,从而避免使用过多的局部变量。 4. 保持函数短小精悍。 5. 不应过分强调注释的作用,应

  20. 1.4.5 库和头文件的保存位置 1. 函数库 * /lib:系统必备共享库 * /usr/lib:标准共享库和静态库 * /usr/i486-linux-libc5/lib:libc5 兼容性函数库 * /usr/X11R6/lib:X11R6 的函数库 * /usr/local/lib:本地函数库 2. 头文件 * /usr/include:系统头文件 * /usr/local/include:本地头文件 3. 共享库及其相关配置 * /etc/ld.so.conf:包含共享库的搜索位置 * ldconfig:共享库管理工具,一般在更新了共享库之后要运行该命令 * ldd:可查看可执行文件所使用的共享库

  21. 1.5 Linux 上进行程序开发的例子 1.5.1 控制台上的开发场景 #DEMO# 在控制台上利用 vim 编辑器编写“Hello, world!程序,并在命令行编译并运行。 1.5.2 X Window 上的开发场景 #DEMO# 在 X Window 上利用 Emacs 编写“Hello, world!程序。

  22. 1.6 程序和脚本 1.6.1 程序:编写, 编译, 调试和执行 #TODO# #DEMO# 1.6.2 脚本:编写, 执行 #TODO# #DEMO#

  23. 1.7 Linux 上的 C/C++ 编译器和调试器 * 运行 gcc/egcs * gcc/egcs 的主要选项 * gdb * gdb 的常用命令 * gdb 使用范例 * 其他程序/库工具 (ar, objdump, nm, size, strings, strip, ...) * 创建和使用静态库 * 创建和使用共享库 * 使用高级共享库特性

  24. 1.7 .1 运行 gcc Linux 中最重要的软件开发工具是 GCC。GCC 是 GNU 的 C 和 C++ 编译器。实际上,GCC 能够编译三种语言:C、C++ 和 Object C(C 语言的一种面向对象扩展)。利用 gcc 命令可同时编译并连接 C 和 C++ 源程序。 如果你有两个或少数几个 C 源文件,也可以方便地利用 GCC 编译、连接并生成可执行文件。例如,假设你有两个源文件 main.c 和 factorial.c 两个源文件,现在要编译生成一个计算阶乘的程序。 ----------------------- 清单 factorial.c ----------------------- #include <stdio.h> #include <stdlib.h> int factorial (int n) { if (n <= 1) return 1; else return factorial (n - 1) * n; } -----------------------

  25. 1.7 .1 运行 gcc ----------------------- 清单 main.c ----------------------- #include <stdio.h> #include <stdlib.h> int factorial (int n); int main (int argc, char **argv) { int n; if (argc < 2) { printf ("Usage: %s n\n", argv [0]); return -1; } else { n = atoi (argv[1]); printf ("Factorial of %d is %d.\n", n, factorial (n)); } return 0; }

  26. 1.7 .1 运行 gcc 利用如下的命令可编译生成可执行文件,并执行程序: $ gcc -o factorial main.c factorial.c $ ./factorial 5 Factorial of 5 is 120.

  27. 1.7 .1 运行 gcc GCC 可同时用来编译 C 程序和 C++ 程序。一般来说,C 编译器通过源文件的后缀名来判断是 C 程序还是 C++ 程序。在 Linux 中,C 源文件的后缀名为 .c,而 C++ 源文件的后缀名为 .C 或 .cpp。 但是,gcc 命令只能编译 C++ 源文件,而不能自动和 C++ 程序使用的库连接。因此,通常使用 g++ 命令来完成 C++ 程序的编译和连接,该程序会自动调用 gcc 实现编译。假设我们有一个如下的 C++ 源文件(hello.C): #include <iostream.h> void main (void) { cout << "Hello, world!" << endl; } 则可以如下调用 g++ 命令编译、连接并生成可执行文件: $ g++ -o hello hello.C $ ./hello Hello, world!

  28. 1.7.2 gcc 的主要选项 选项 解释 -ansi 只支持 ANSI 标准的 C 语法。这一选项将禁止 GNU C 的某些特色, 例如 asm 或 typeof 关键词。 -c 只编译并生成目标文件。 -DMACRO 以字符串“1”定义 MACRO 宏。 -DMACRO=DEFN 以字符串“DEFN”定义 MACRO 宏。 -E 只运行 C 预编译器。 -g 生成调试信息。GNU 调试器可利用该信息。 -IDIRECTORY 指定额外的头文件搜索路径DIRECTORY。 -LDIRECTORY 指定额外的函数库搜索路径DIRECTORY。 -lLIBRARY 连接时搜索指定的函数库LIBRARY。 -m486 针对 486 进行代码优化。 -o FILE 生成指定的输出文件。用在生成可执行文件时。 -O0 不进行优化处理。 -O 或 -O1 优化生成代码。 -O2 进一步优化。 -O3 比 -O2 更进一步优化,包括 inline 函数。 -shared 生成共享目标文件。通常用在建立共享库时。 -static 禁止使用共享连接。 -UMACRO 取消对 MACRO 宏的定义。 -w 不生成任何警告信息。 -Wall 生成所有警告信息。

  29. 1.7.2 gcc 的主要选项 #DEMO -O 优化的效果 #include <stdio.h> long accu(long n) { long i=0; while (i<n) { i++; } return i; } int main(int argc,char * argv[]) { long n; n=accu(10000000); printf("%d\n",n); return 0; }

  30. 1.7.3 gdb GNU 的调试器称为 gdb,该程序是一个交互式工具,工作在字符模式。在 X Window 系统中,有一个 gdb 的前端图形工具,称为 xxgdb。 gdb 是功能强大的调试程序,可完成如下的调试任务: * 设置断点; * 监视程序变量的值; * 程序的单步执行; * 修改变量的值。 在可以使用 gdb 调试程序之前,必须使用 -g 选项编译源文件。可在 makefile 中如下定义 CFLAGS 变量: CFLAGS = -g

  31. 1.7.3 gdb 运行 gdb 调试程序时通常使用如下的命令: gdb progname 在 gdb 提示符处键入help,将列出命令的分类,主要的分类有: * aliases:命令别名 * breakpoints:断点定义; * data:数据查看; * files:指定并查看文件; * internals:维护命令; * running:程序执行; * stack:调用栈查看; * statu:状态查看; * tracepoints:跟踪程序执行。 键入 help 后跟命令的分类名,可获得该类命令的详细清单。 #DENO#

  32. 1.7.4 gdb 的常用命令 命令 解释 break NUM 在指定的行上设置断点。 bt 显示所有的调用栈帧。该命令可用来显示函数的调用顺序。 clear 删除设置在特定源文件、特定行上的断点。其用法为:clear FILENAME:NUM。 continue 继续执行正在调试的程序。该命令用在程序由于处理信号或断点而导致停止运行时。 display EXPR 每次程序停止后显示表达式的值。表达式由程序定义的变量组成。 file FILE 装载指定的可执行文件进行调试。 help NAME 显示指定命令的帮助信息。 info break 显示当前断点清单,包括到达断点处的次数等。 info files 显示被调试文件的详细信息。 info func 显示所有的函数名称。 info local 显示当函数中的局部变量信息。 info prog 显示被调试程序的执行状态。 info var 显示所有的全局和静态变量名称。 kill 终止正被调试的程序。 list 显示源代码段。 make 在不退出 gdb 的情况下运行 make 工具。 next 在不单步执行进入其他函数的情况下,向前执行一行源代码。 print EXPR 显示表达式 EXPR 的值。

  33. 1.7.5 gdb 使用范例 ----------------- 清单 一个有错误的 C 源程序 bugging.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 错误:

  34. 1.7.5 gdb 使用范例 $ gcc -o test -g test.c $ ./test Please input a string: asfd Segmentation fault (core dumped) 为了查找该程序中出现的问题,我们利用 gdb,并按如下的步骤进行: 1.运行 gdb bugging 命令,装入 bugging 可执行文件; 2.执行装入的 bugging 命令; 3.使用 where 命令查看程序出错的地方; 4.利用 list 命令查看调用 gets 函数附近的代码; 5.唯一能够导致 gets 函数出错的因素就是变量 string。用 print 命令查看 string 的值; 6.在 gdb 中,我们可以直接修改变量的值,只要将 string 取一个合法的指针值就可以了,为 此,我们在第 10 行处设置断点; 7.程序重新运行到第 10 行处停止,这时,我们可以用 set variable 命令修改 string 的取值; 8.然后继续运行,将看到正确的程序运行结果。

  35. 1.7.5 gdb 使用范例 ~/src/test $ gdb bug GNU gdb 5...... (gdb) b 10 Breakpoint 1 at 0x8048390: file bug.c, line 10. (gdb) r Starting program: /home/slade/src/test/bug Breakpoint 1, main () at bug.c:10 10 gets (string); (gdb) print string $1 = 0x0 (gdb) set variable string=&buff (gdb) print string $2 = 0x8049640 "" (gdb) c Continuing. Please input a string: Hello yet another world! Your string is: Hello yet another world! Program exited normally. (gdb) q

  36. 1.7.6 其他程序/库工具 * strip: 去除二进制代码中的符号表等附加信息 * nm: 列出二进制代码中的符号表 * size: 列出二进制代码中各段的大小 * string: 列出二进制代码中的字符串

  37. 1.7.7 创建和使用静态库 创建一个静态库是相当简单的。通常使用 ar 程序把一些目标文件(.o)组合在一起,成为一个单独的库,然后运行 ranlib,以给库加入一些索引信息。 gcc -c -o mine.o mine.c gcc -c -o your.o your.c ar r libour.a our.o ranlib libour.a gcc -o test test.c -lour

  38. 1.7.8 创建和使用共享库 特殊的编译和连接选项 -D_REENTRANT 使得预处理器符号 _REENTRANT 被定义,这个符号激活一些宏特性。 -fPIC 选项产生位置独立的代码。由于库是在运行的时候被调入,因此这个选项是必需的,因为在编译的时候,装入内存的地址还不知道。如果不使用这个选项,库文件可能不会正确运行。 -shared 选项告诉编译器产生共享库代码。 -Wl,-soname=<SONAME> -Wl 告诉编译器将后面的参数传递到连接器。而 -soname 指定了共享库的 soname。 # 可以把库文件拷贝到 /etc/ld.so.conf 中列举出的任何目录中,并以 root 身份运行 ldconfig;或者 # 运行 export LD_LIBRARY_PATH='pwd',它把当前路径加到库搜索路径中去。

  39. 1.7.9 使用高级共享库特性 1. ldd 工具 ldd 用来显示执行文件需要哪些共享库, 共享库装载管理器在哪里找到了需要的共享库. 2. soname 共享库的一个非常重要的,也是非常难的概念是 soname--简写共享目标名(short for shared object name)。这是一个为共享库(.so)文件而内嵌在控制数据中的名字。如前面提到的,每一个程序都有一个需要使用的库的清单。这个清单的内容是一系列库的 soname,如同 ldd 显示的那样,共享库装载器必须找到这个清单。 soname 的关键功能是它提供了兼容性的标准。当要升级系统中的一个库时,并且新库的 soname 和老的库的 soname 一样,用旧库连接生成的程序,使用新的库依然能正常运行。这个特性使得在 Linux 下,升级使用共享库的程序和定位错误变得十分容易。 在 Linux 中,应用程序通过使用 soname,来指定所希望库的版本。库作者也可以通过保留或者改变 soname 来声明,哪些版本是相互兼容的,这使得程序员摆脱了共享库版本冲突问题的困扰。

  40. 1.7.9 使用高级共享库特性 3. 共享库装载器 当程序被调用的时候,Linux 共享库装载器(也被称为动态连接器)也自动被调用。它的作用是保证程序所需要的所有适当版本的库都被调入内存。共享库装载器名字是 ld.so 或者是 ld-linux.so,这取决于 Linux libc 的版本,它必须使用一点外部交互,才能完成自己的工作。然而它接受在环境变量和配置文件中的配置信息。 文件 /etc/ld.so.conf 定义了标准系统库的路径。共享库装载器把它作为搜索路径。为了改变这个设置,必须以 root 身份运行 ldconfig 工具。这将更新 /etc/ls.so.cache 文件,这个文件其实是装载器内部使用的文件之一。

  41. 共享库装载器 可以使用许多环境变量控制共享库装载器的操作。 共享库装载器环境变量 变量 含义 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 中指定了全局版本号,该文件不遵守这个限制。

  42. 使用 dlopen 4. 使用 dlopen 另外一个强大的库函数是 dlopen()。该函数将打开一个新库,并把它装入内存。该函数主要用来加载库中的符号,这些符号在编译的时候是不知道的。比如 Apache Web 服务器利用这个函数在运行过程中加载模块,这为它提供了额外的能力。一个配置文件控制了加载模块的过程。这种机制使得在系统中添加或者删除一个模块时,都不需要重新编译了。 可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定义,并在 dl 库中实现。它需要两个参数:一个文件名和一个标志。文件名可以是我们学习过的库中的 soname。标志指明是否立刻计算库的依赖性。如果设置为 RTLD_NOW 的话,则立刻计算;如果设置的是 RTLD_LAZY,则在需要的时候才计算。另外,可以指定 RTLD_GLOBAL,它使得那些在以后才加载的库可以获得其中的符号。 当库被装入后,可以把 dlopen() 返回的句柄作为给 dlsym() 的第一个参数,以获得符号在库中的地址。使用这个地址,就可以获得库中特定函数的指针,并且调用装载库中的相应函数。

  43. 1.8 GNU make 和 makefile * GNU make * makefile 基本结构 * makefile 变量 * GNU make 的主要预定义变量 * 隐含规则 * makefile 范例 * 运行 make

  44. 1.8.1 GNU make 在大型的开发项目中,通常有几十到上百个的源文件,如果每次均手工键入 gcc 命令进行编译的话,则会非常不方便。因此,人们通常利用 make 工具来自动完成编译工作。这些工作包括:如果仅修改了某几个源文件,则只重新编译这几个源文件;如果某个头文件被修改了,则重新编译所有包含该头文件的源文件。 利用这种自动编译可大大简化开发工作,避免不必要的重新编译。 实际上,make 工具通过一个称为 makefile 的文件来完成并自动维护编译工作。makefile 需要按照某种语法进行编写,其中说明了如何编译各个源文件并连接生成可执行文件,并定义了源文件之间的依赖关系。 当修改了其中某个源文件时,如果其他源文件依赖于该文件,则也要重新编译所有依赖该文件的源文件。 makefile 文件是许多编译器,包括 Windows NT 下的编译器维护编译信息的常用方法,只是在集成开发环境中,用户通过友好的界面修改 makefile 文件而已。 默认情况下,GNU make 工具在当前工作目录中按如下顺序搜索 makefile: * GNUmakefile * makefile * Makefile 在 UNIX 系统中,习惯使用 Makefile 作为 makfile 文件。如果要使用其他文件作为 makefile,则可利用类似下面的 make 命令选项指定 makefile 文件: $ make -f Makefile.debug

  45. 1.8.2 makefile 基本结构 makefile 中一般包含如下内容: * 需要由 make 工具创建的项目,通常是目标文件和可执行文件。通常使用“目标(target)”一词来表示 要创建的项目。 * 要创建的项目依赖于哪些文件。 * 创建每个项目时需要运行的命令。 例如,假设你现在有一个 C++ 源文件 test.C,该源文件包含有自定义的头文件 test.h,则目标文件 test.o 明确依赖于两个源文件:test.C 和 test.h。另外,你可能只希望利用 g++ 命令来生成 test.o 目标文件。 这时,就可以利用如下的 makefile 来定义 test.o 的创建规则: # This makefile just is a example. # The following lines indicate how test.o depends # test.C and test.h, and how to create test.o test.o: test.C test.h g++ -c -g test.C

  46. 1.8.2 makefile 基本结构 从上面的例子注意到,第一个字符为 # 的行为注释行。第一个非注释行指定 test.o 为目标,并且依赖于test.C 和 test.h 文件。随后的行指定了如何从目标所依赖的文件建立目标。 当 test.C 或 test.h 文件在编译之后又被修改,则 make 工具可自动重新编译 test.o,如果在前后两次编译之间,test.C 和 test.h 均没有被修改,而且 test.o 还存在的话,就没有必要重新编译。这种依赖关系在多源文件的程序编译中尤其重要。通过这种依赖关系的定义,make 工具可避免许多不必要的编译工作。当然,利用 Shell 脚本也可以达到自动编译的效果,但是,Shell 脚本将全部编译任何源文件,包括哪些不必要重新编译的源文件,而 make 工具则可根据目标上一次编译的时间和目标所依赖的源文件的更新时间而自动判断应当编译哪个源文件。 一个 makefile 文件中可定义多个目标,利用 make target 命令可指定要编译的目标,如果不指定目标,则使用第一个目标。通常,makefile 中定义有 clean 目标,可用来清除编译过程中的中间文件,例如: clean: rm -f *.o 运行 make clean 时,将执行 rm -f *.o 命令,最终删除所有编译过程中产生的所有中间文件。

  47. 1.8.3 makefile 变量 GNU 的 make 工具除提供有建立目标的基本功能之外,还有许多便于表达依赖性关系以及建立目标的命令的特 色。其中之一就是变量或宏的定义能力。如果你要以相同的编译选项同时编译十几个 C 源文件,而为每个目 标的编译指定冗长的编译选项的话,将是非常乏味的。但利用简单的变量定义,可避免这种乏味的工作: # Define macros for name of compiler CC = gcc # Define a macr o for the CC flags CCFLAGS = -D_DEBUG -g -m486 # A rule for building a object file test.o: test.c test.h $(CC) -c $(CCFLAGS) test.c 在上面的例子中,CC 和 CCFLAGS 就是 make 的变量。GNU make 通常称之为变量,而其他 UNIX 的 make 工具称之为宏,实际是同一个东西。在 makefile 中引用变量的值时,只需变量名之前添加 $ 符号,如 上面的 $(CC) 和 $(CCFLAGS)。

  48. 1.8.4 GNU make 的主要预定义变量 GNU make 有许多预定义的变量,这些变量具有特殊的含义,可在规则中使用。表 1-5 给出了一些主要的预定义变量,除这些变量外,GNU make 还将所有的环境变量作为自己的预定义变量。 预定义变量 含义 $* 不包含扩展名的目标文件名称。 $+ 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。 $< 第一个依赖文件的名称。 $? 所有的依赖文件,以空格分开,这些依赖文件的修改日期比目标的创建日期晚。 $@ 目标的完整名称。 $^ 所有的依赖文件,以空格分开,不包含重复的依赖文件。 $% 如果目标是归档成员,则该变量表示目标的归档成员名称。例如,如果目标名称 为 mytarget.so(image.o),则 $@ 为 mytarget.so,而 $% 为 image.o。 AR 归档维护程序的名称,默认值为 ar。 ARFLAGS 归档维护程序的选项。 AS 汇编程序的名称,默认值为 as。 ASFLAGS 汇编程序的选项。 CC C 编译器的名称,默认值为 cc。 CFLAGS C 编译器的选项。 CPP C 预编译器的名称,默认值为 $(CC) -E。 CPPFLAGS C 预编译的选项。 CXX C++ 编译器的名称,默认值为 g++。 CXXFLAGS C++ 编译器的选项。 FC FORTRAN 编译器的名称,默认值为 f77。 FFLAGS FORTRAN 编译器的选项。

  49. 1.8.5 隐含规则 GNU make 包含有一些内置的或隐含的规则,这些规则定义了如何从不同的依赖文件建立特定类型的目标。 GNU make 支持两种类型的隐含规则: * 后缀规则(Suffix Rule)。后缀规则是定义隐含规则的老风格方法。后缀规则定义了将一个具有某个后缀的文件(例如,.c 文件)转换为具有另外一种后缀的文件(例如,.o 文件)的方法。每个后缀规则以两个成对出现的后缀名定义,例如,将 .c 文件转换为 .o 文件的后缀规则可定义为: .c.o: $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $< * 模式规则(pattern rules)。这种规则更加通用,因为可以利用模式规则定义更加复杂的依赖性规则。 模式规则看起来非常类似于正则规则,但在目标名称的前面多了一个 % 号,同时可用来定义目标和依赖 文件之间的关系,例如下面的模式规则定义了如何将任意一个 X.c 文件转换为 X.o 文件: %.c:%.o $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

  50. 1.8.6 makefile 范例 #SAMPLE#

More Related