Chinaunix首页 | 论坛 | 博客
  • 博客访问: 140007
  • 博文数量: 49
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 595
  • 用 户 组: 普通用户
  • 注册时间: 2008-11-25 23:08
文章分类
文章存档

2011年(1)

2009年(48)

我的朋友

分类: LINUX

2009-03-08 05:07:49

 

8 linux编程基础

1.概述

1.1 Linux 编程

Linux软件开发一直在Internet环境下讲行。这个环境是全球性的,编程人员来自世界各地。只要能够访问Web站点,就可以启动一个以Linux为基础的软件项目。Linux开发工作经常是在Linux用户决定共同完成一个项目时开始的。当开发工作完成后,该软件就被放到Internet站点上,任何用户都可以访问和下载它。由于这个活跃的开发环境,新的以Linux为基础的软件功能日益强大,而且呈现爆炸式的增长态势。

大多数Linux软件是经过自由软件基金会(Free Software Foundation)提供的GNUGNUGNU’s not UNIX)公开认证授权的,因而通常被称作GNU软件。GNU软件免费提供给用户使用,并被证明是非常可靠和高效的。许多流行的Linux实用程序如C编译器、shell和编辑器都是GNU软件应用程序。

Linux程序需要首先转化为低级机器语言即所谓的二进制代码以后,才能被操作系统执行。例如编程时,先用普通的编程语言生成一系列指令,这些指令可被翻译为适当的可执行应用程序的二进制代码。这个翻译过程可由解释器一步步来完成,或者也可以立即由编译器明确地完成。shell编程语言如BASHTCSHGAWKPerlTclTk都利用自己的解释器。用这些语言编制的程序尽管是应用程序文件,但可以直接运行。编译器则不同,它将生成一个独立的二进制代码文件然后才可以运行。

1GNU风格

1)函数返回类型说明和函数名分两行放置,函数起始字符和函数开头左花括号放到最左边。

2)尽量不要让两个不同优先级的操作符出现在相同的对齐方式中,应该附加额外的括号使得代码缩进可以表示出嵌套。

3)按照规定方式排版do-while语句:

4)每个程序都应该以一段简短的说明其功能的注释开头。

5)请为每个函数书写注释,说明函数是做什么的,需要哪些入口参数,参数可能值的含义和用途。如果用了非常见的、非标准的东西,或者可能导致函数不能工作的任何可能的值,应该进行特殊说明。如果存在重要的返回值,也需要说明。

6)不要声明多个变量时跨行,每一行都以一个新的声明开头。

7)当一个if中嵌套了另一个if-else时,应用花括号把ifelse括起来。

8)要在同一个声明中同时说明结构标识和变量或者结构标识和类型定义(typedef)。先定义变量,再使用。

9)尽量避免在if的条件中进行赋值。

10)请在名字中使用下划线以分割单词,尽量使用小写;把大写字母留给宏和枚举常量,以及根据统一惯例使用的前缀。例如,应该使用类似ignore_space_change_flag的名字;不要使用类似iCantReadThis的名字

2Linux 内核编程风格

1Linux内核缩进风格是8个字符。

2Linux内核风格采用K&R标准,将开始的大括号放在一行的最后,而将结束的大括号放在一行的第一位。

3)命名尽量简洁。不应该使用诸如ThisVariableIsATemporaryCounter之类的名字。应该命名为tmp,这样容易书写,也不难理解。但是命名全局变量,就应该用描述性命名方式,例如应该命名“count_active_users()”,而不是“cntusr()”。本地变量应该避免过长。。

2. Vi 的使用

vi提供了一些功能强大的但容易记忆的命令供用户使用。类似这样的编辑任务在vi中可以轻松高效完成。

Vi 3 种模式,分别为命令行模式、插入模式及命令行模式各模式的功能,

下面具体进行介绍。

1)命令行模式

用户在用Vi 编辑文件时,最初进入的为一般模式。在该模式中可以通过上下移动光标进行“删除字符”或“整行删除”等操作,也可以进行“复制”、“粘贴”等操作,但无法编辑文字。

2)插入模式

只有在该模式下,用户才能进行文字编辑输入,用户课按[ESC]键回到命令行模式。(在命令行模式按下 i 键进入,注意左下脚的变化)

3)底行模式

在该模式下,光标位于屏幕的底行。用户可以进行文件保存或退出操作,也可以设置编辑环境,如寻找字符串、列出行号等。

编辑命令

命 令

功 能

[N]x

(Expurgate)删除从光标位置开始的连续N个字符(并复制到编辑缓冲区)

[N]dd

(Delete)删除从光标位置开始的连续N行(并复制到编辑缓冲区)

[N]yy

(Yank)复制从光标位置开始的连续N行到编辑缓冲区

p

(Put)从编辑缓冲区复制文本到当前光标位置(即粘贴)

u

(Undo)取消上一次操作(即恢复功能)

光标命令

命 令

功 能

h

方向键,向左移动光标一个字符的位置,相当于键“←”

j

方向键,向下移动光标到下一行的位置,相当于键“↓”

k

方向键,向上移动光标到上一行的位置,相当于键“↑”

l

方向键,向右移动光标一个字符的位置,相当于键“→”

:N

移动光标到第N行(N待定)

1G

移动光标到文件的第1

G

移动光标到文件的最后1

:set number

设置显示行号

:set nonumber

取消显示行号

文件命令

命 令

功 能

:q (Quit)

退出没有被修改的文件(若文件被修改了而没有保存,则此命令无效)

:q!

强制退出,且不保存修改过的部分

:w (Write)

保存文件,但不退出

:w!

强制保存文件,但不退出

:x (Exit)

保存文件并退出

:x!

强制保存文件并退出

:w File

另存为File给出的文件名,不退出

:w! File

强制另存为File给出的文件名,不退出

:r File (Read)

读入File指定的文件内容插入到光标位置

状态命令

命 令

功 能

a

(Append)进入编辑状态,从当前光标之后的位置开始插入键盘输入的字符

i

(Insert)进入编辑状态,从当前光标之后的位置开始插入键盘输入的字符

o

(Open)进入编辑状态,并插入一新行,光标移到该新行的行首,以后键盘输入的字符将插入到光标位置

ESC

进入命令状态

:!Command

vi中执行外部命令Command,按回车键可以返回vi继续工作

3. 使用GNU cc 开发应用程序

3.1 使用GNU cc

gcc可以使程序员灵活地控制编译过程。编译过程一般可以分为下面四个阶段,每个阶段分别调用不同的工具进行处理,如图9-18所示。



3.2 gcc的版本信息

 一般来说,系统安装后就已经安装和设定好了gcc。在shell的提示符下键入gcc -v,屏幕上就会显示出目前正在使用的gcc的版本,同时这可以确定系统所支持的是ELF还是a.out可执行文件格式。

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格式的系统上。

3.3 gcc 的使用

gcc的使用格式如下:

$ gcc [options][filenames]

 其中filenames为所要编译的程序源文件。

当使用gcc时,gcc会完成预处理、编译、汇编和连接。前三步分别生成目标文件,连接时,把生成的目标文件链接成可执行文件。gcc可以针对支持不同的源程序文件进行不同处理,文件格式以文件的后缀来识别。

Gcc总体选项列表

后 缀 名

所对应的语言

-c

只是编译不链接,生成目标文件“.o”

-S

只是编译不汇编,生成汇编代码

-E

只进行预编译,不做其他处理

-g

在可执行程序中包含标准调试信息

-o file

把输出文件输出到file

-v

打印出编译器内部编译各过程的命令行信息和编译器的版本

-I dir

在头文件的搜索路径列表中添加dir目录

-L dir

在库文件的搜索路径列表中添加dir目录

-static

链接静态库

-llibrary

连接名为library的库文件

3.4 使用优化选项

 当用gcc编译C代码时,它会试着用最少的时间完成编译并且使编译后的代码易于调试. 易于调试意味着编译后的代码与源代码有同样的执行次序,编译后的代码没有经过优化。有很多选项可用于告诉gcc,在耗费更多编译时间和牺牲易调试性的基础上,产生更小更快的可执行文件。这些选项中最典型的是-O-O2选项。

-O选项告诉gcc对源代码进行基本优化。这些优化在大多数情况下都会使程序执行的更快。

-O2选项告诉gcc产生尽可能小和尽可能快的代码。-O2选项将使编译的速度比使用-O 时慢。但通常产生的代码执行速度会更快。

3.5 使用调试和剖析选项

GCC 支持数种调试和剖析选项。在这些选项里最常用的是-g-pg选项。

-g选项告诉gcc产生能被GNU调试器使用的调试信息以便调试程序。gcc 提供了一个很多其他C编译器里没有的特性,在gcc里能使-g-O(产生优化代码)连用。这一点非常有用,因为能在与最终产品尽可能相近的情况下调试代码。同时使用这两个选项时必须清楚所写的某些代码已经在优化时被gcc作了改动。

-pg选项告诉gcc在程序里加入额外的代码,执行时,产生gprof用的剖析信息以显示程序的耗时情况

4. 使用GNU make编辑makefile

4.1 准备工作

要使用make,必须编写一个叫做Makefile的文件,这个文件描述了软件包中文件之间的关系,提供更新每个文件的命令。一般在一个软件包里,通常是可执行文件靠目标文件来更新,目标文件靠编译源文件来更新。

Makefile写好之后,每次改变了某些源文件,只要执行make命令:

# make

所有必要的重新编译将执行。Make程序利用makefile中的数据和每个文件的最后修改时间来确定那个文件需要更新,对于需要更新的文件,make程序执行makefile数据中定义的命令来更新。。

4.2 makefile 文件的基本结构

GNU make的主要功能是读进一个文本文件makefile并根据makefile的内容执行一系列的工作。makefile的默认文件名为GNUmakefilemakefileMakefile,当然也可以在make的命令行中指定别的文件名。如果不特别指定,make命令在执行时将按顺序查找默认的makefile文件。多数Linux程序员使用第三种文件名Makefile。因为第一个字母是大写,通常被列在一个目录的文件列表的最前面。

Makefile是一个文本形式的数据库文件,其中包含一些规则来告诉make处理哪些文件以及如何处理这些文件。这些规则主要是描述哪些文件(称为target目标文件,不要和编译时产生的目标文件相混淆)是从哪些别的文件(称为dependency依赖文件)中产生的,以及用什么命令(command)来执行这个过程。

依靠这些信息,make会对磁盘上的文件进行检查,如果目标文件的生成或被改动时的时间(称为该文件时间戳)至少比它的一个依赖文件还旧的话,make就执行相应的命令,以更新目标文件。目标文件不一定是最后的可执行文件,可以是任何一个中间文件并可以作为其他目标文件的依赖文件。

一个Makefile文件主要含有一系列的规则,每条规则包含以下内容。

一个目标(target),即make最终需要创建的文件,如可执行文件和目标文件;目标也可以是要执行的动作,如“clean”

一个或多个依赖文件(dependency)列表,通常是编译目标文件所需要的其他文件。

一系列命今(command),是make执行的动作,通常是把指定的相关文件编译成目标文件的编译命令,每个命令占一行,且每个命令行的起始字符必须为TAB 字符。

例如,有以下的Makefile文件:

# 一个简单的Makefile的例子

# #开头的为注释行

testprog.o code.o

gcc –o test prog.o code.o

prog.oprog.c prog.h code.h

gcc –c prog.c –o prog.o

code.ocode.c code.h

gcc –c code.c –o code.o

clean

rm –f *.o

上面的Makefile文件中共定义了四个目标:testprog.ocode.oclean。目标从每行的最左边开始写,后面跟一个冒号(:),如果有与这个目标有依赖性的其他目标或文件,把它们列在冒号后面,并以空格隔开。然后另起一行开始写实现这个目标的一组命令。在Makefile中,可使用续行号(\)将一个单独的命令行延续成几行。但要注意在续行号(\)后面不能跟任何字符(包括空格和键)。

一般情况下,调用make命令可输入:

# make target

targetMakefile文件中定义的目标之一,如果省略targetmake就将生成Makefile文件中定义的第一个目标。对于上面Makefile的例子,单独的一个“make”命令等价于:

# make test

因为testMakefile文件中定义的第一个目标,make首先将其读入,然后从第一行开始执行,把第一个目标test作为它的最终目标,所有后面的目标的更新都会影响到test的更新。第一条规则说明只要文件test的时间戳比文件prog.ocode.o中的任何一个旧,下一行的编译命令将会被执行。

但是,在检查文件prog.ocode.o的时间戳之前,make会在下面的行中寻找以prog.ocode.o为目标的规则,在第三行中找到了关于prog.o的规则,该文件的依赖文件是prog.cprog.hcode.h。同样,make会在后面的规则行中继续查找这些依赖文件的规则,如果找不到,则开始检查这些依赖文件的时间戳,如果这些文件中任何一个的时间戳比prog.o的新,make将执行“gcc –c prog.c –o prog.o”命令,更新prog.o文件。

以同样的方法,接下来对文件code.o做类似的检查,依赖文件是code.ccode.h。当make执行完所有这些套嵌的规则后,make将处理最顶层的test规则。如果关于prog.ocode.o的两个规则中的任何一个被执行,至少其中一个.o目标文件就会比test新,那么就要执行test规则中的命令,因此make去执行gcc命令将prog.ocode.o连接成目标文件test

在上面Makefile的例子中,还定义了一个目标clean,它是Makefile中常用的一种专用目标,即删除所有的目标模块

现在来看一下make做的工作:首先make按顺序读取makefile中的规则,然后检查该规则中的依赖文件与目标文件的时间戳哪个更新,如果目标文件的时问戳比依赖文件还早,就按规则中定义的命令更新目标文件。如果该规则中的依赖文件又是其他规则中的目标文件,那么依照规则链不断执行这个过程,直到Makefile文件的结束,至少可以找到一个不是规则生成的最终依赖文件,获得此文件的时间戳,然后从下到上依照规则链执行目标文件的时间戳比此文件时间戳旧的规则,直到最顶层的规则。

通过以上的分析过程,可以看到make的优点,因为.o目标文件依赖.c源文件,源码文件里一个简单改变都会造成那个文件被重新编译,并根据规则链依次由下到上执行编译过程,直到最终的可执行文件被重新连接。例如,当改变一个头文件的时候,由于所有的依赖关系都在Makefile里,因此不再需要记住依赖此头文件的所有源码文件,make可以自动的重新编译所有那些因依赖这个头文件而改变了的源码文件,如果需要,再进行重新连接。

4.3 Makefile中的变量

Makefile里的变量就像一个环境变量。事实上,环境变量在make中也被解释成make的变量。这些变量对大小写敏感,一般使用大写宇母。几乎可以从任何地方引用定义的变量

,变量的主要作用如下:

保存文件名列表。在前面的例子里,作为依赖文件的一些目标文件名出现在可执行文件的规则中,而在这个规则的命令行里同样包含这些文件并传递给gcc做为命令参数。如果使

用一个变量来保存所有的目标文件名,则可以方便地加入新的目标文件而且不易出错。

保存可执行命令名,如编译器。在不同的

Linux系统中存在着很多相似的编译器系统,这些系统在某些地方会有细微的差别,如果项目被用在一个非gcc的系统里,则必须将所有出现编译器名的地方改成用新的编译器名。但是如果使用一个变量来代替编译器名,那么只需要改变该变量的值。其他所有地方的命令名就都改变了。

保存编译器的参数。在很多源代码编译时,gcc需要很长的参数选项,在很多情况下,所有的编译命令使用一组相同的选项,如果把这组选项使用一个变量代表,那么可以把这个变量放在所有引用编译器的地方。当要改变选项的时候,只需改变一次这个变量的内容即可。

Makefile中的变量是用一个文本串在Makefile中定义的,这个文本串就是变量的值。只要在一行的开始写下这个变量的名字,后面跟一个“=”号,以及要设定这个变量的值即可定义变量,下面是定义变量的语法:VARNAME=string

使用时,把变量用括号括起来,并在前面加上$符号,就可以引用变量的值:${VARNAME}

make解释规则时,VARNAME在等式右端展开为定义它的字符串。变量一般都在Makefile的头部定义。按照惯例,所有的Makefile变量都应该是大写。如果变量的值发生变化,就只需要在一个地方修改,从而简化了Makefile的维护。

现在利用变量把前面的Makefile重写一遍:

OBJS=kang.o yul.o

CC=gcc

CFLAGS=-Wal -O -g

test${ OBJS }

${ CC } ${CFLAGS } –o test

prog.oprog.c prog.h code.h

${ CC } ${CFLAGS } –o prog.o

code.ocode.c code.h

${ CC } –c code.c –o code.o

clean

rm –f *.o



sunq:kang.o yul.o

Gcc kang.o bar.o -o myprog

kang.o : kang.c kang.h head.h

Gcc –Wall –O -g –c kang.c -o kang.o

yul.o : bar.c head.h

Gcc - Wall –O -g –c yul.c -o yul.o



OBJS = kang.o yul.o

CC = gcc

CFLAGS = -Wall -O -g

sunq : $(OBJS)

$(CC) $(OBJS) -o sunq

kang.o : kang.c kang.h

$(CC) $(CFLAGS) -c kang.c -o kang.o

yul.o : yul.c yul.h

$(CC) $(CFLAGS) -c yul.c -o yul.o

除用户自定义的变量外,make还允许使用环境变量、自动变量和预定义变量。使用环境变量的方法很简单,在make启动时,make读取系统当前已定义的环境变量,并且创建与之同名同值的变量,因此用户可以像在shell中一样在Makefile中方便的引用环境变量。需要注意的是,如果用户在Makefile中定义了同名的变量,用户自定义变量将覆盖同名的环境变量。此外,Makefile中还有一些预定义变量和自动变量,但是看起来并不像自定义变量那样直观。

4.4 Makefile 的隐含规则

在上面的例子中,几个产生目标文件的命令都是从“.c”C语言源文件和相关文件通过编译产生“.o”目标文件,这也是一般的步骤。实际上,make可以使工作更加自动化,也就是说,make知道一些默认的动作,它有一些称作隐含规则的内置的规则,这些规则告诉make当用户没有完整地给出某些命令的时候,应该怎样执行。

例如,把生成prog.ocode.o的命令从规则中删除,make将会查找隐含规则,然后会找到并执行一个适当的命令。由于这些命令会使用一些变量,因此可以通过改变这些变量来定制make。象在前面的例子中所定义的那样,make使用变量CC来定义编译器,并且传递变量

CFLAGS(编译器参数)、CPPFLAGSC语言预处理器参数)、TARGET_ARCH(目标机器的结构定义)给编译器,然后加上参数-c,后面跟变量$<(第一个依赖文件名),然后是参数-o加变量$@(目标文件名)。综上所述,

一个C编译的具体命令将会是:

$ {CC} $ {CFLAGS} $ {CPPFLAGS} $ {TARGET_ARCH} –c

$< -o $@

Makefile中常见预定义变量

命令格式

含 义

AR

库文件维护程序的名称,默认值为ar

AS

汇编程序的名称,默认值为as

CC

C编译器的名称,默认值为cc

CPP

C预编译器的名称,默认值为$(CC) –E

CXX

C++编译器的名称,默认值为g++

FC

FORTRAN编译器的名称,默认值为f77

RM

文件删除程序的名称,默认值为rm –f

ARFLAGS

库文件维护程序的选项,无默认值

ASFLAGS

汇编程序的选项,无默认值

CFLAGS

C编译器的选项,无默认值

CPPFLAGS

C预编译的选项,无默认值

CXXFLAGS

C++编译器的选项,无默认值

FFLAGS

FORTRAN编译器的选项,无默认值

Makefile中常见自动变量

命令格式

含 义

$*

不包含扩展名的目标文件名称

$+

所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件

$<

第一个依赖文件的名称

$?

所有时间戳比目标文件晚的依赖文件,并以空格分开

$@

目标文件的完整名称

$^

所有不重复的依赖文件,以空格分开

$%

如果目标是归档成员,则该变量表示目标的归档成员名称

在上面的例子中,利用隐含规则,可以简化为:

OBJS=prog.o code.o

CC=gcc

test${ OBJS }

${ CC } –o $@ $^

prog.oprog.c prog.h code.h

code.ocode.c code.h

clean

rm –f *.o

5 调试工具GDB

5.1 GDB调试器简介

Linux系统中包含了GNU 调试程序gdb,它是一个用来调试CC++ 程序的调试器。可以使程序开发者在程序运行时观察程序的内部结构和内存的使用情况。

gdb 所提供的一些功能如下所示:

运行程序,设置所有的能影响程序运行的参数和环境;

控制程序在指定的条件下停止运行;

当程序停止时,可以检查程序的状态;

修改程序的错误,并重新运行程序;

动态监视程序中变量的值;

可以单步执行代码,观察程序的运行状态

gdb的功能非常强大,到目前为止,gdb已能够支持Moduls-2ChillPascalFORTRAN程序的调试,但是调试这些语言的源程序时有一些功能还不能使用。

例如调试FORTRAN程序时还不支持表达式的输入、输出变量或类FORTRAN的词法。

gdb程序调试的对象是可执行文件,而不是程序的源代码文件。然而,并不是所有的可执行文件都可以用gdb调试。如果要让产生的可执行文件可以用来调试,需在执行gcc指令编译程序时,加上-g参数,指定程序在编译时包含调试信息。调试信息包含程序里的每个变量的类型和在可执行文件里的地址映射以及源代码的行号。gdb 利用这些信息使源代码和机器码相关联

5.2 GDB 命令的基本使用和应用

1gdb基本命令

 还可以用下面的方式来运行gdb:gdb filename

 其中,filename是要调试的可执行文件。用这种方式运行gdb可以直接指定想要调试的程序。这和启动gdb后执行file filename命令效果完全一样。也可以用gdb去检查一个因程序异常终止而产生的core文件,或者与一个正在运行的程序相连。

gdb支持很多的命令且能实现不同的功能。这些命令从简单的文件装入到允许你检查所调用的堆栈内容的复杂命令, 下面列出了在使用gdb 调试时会用到的一些命令。

1file命令:装入想要调试的可执行文件。

2cd命令:改变工作目录。

3pwd命令:返回当前工作日录。

4run命令:执行当前被调试的程序。

5kill命令:停止正在调试的应用程序。

6list命令:列出正在调试的应用程序的源代码。

7break命令:设置断点。

8Tbreak命令;设置临时断点。它的语法与break相同。区别在于用tbreak设置的断点执行一次之后立即消失。

9watch命令:设置监视点,监视表达式的变化。

10awatch命令:设置读写监视点。当要监视的表达式被读或写时将应用程序挂起。它的语法与watch命令相同。

11rwatch命令:设置读监视点,当监视表达式被读时将程序挂起,等侍调试。此命令的语法与watch相同。

12next命令:执行下一条源代码,但是不进入函数内部。也就是说,将一条函数调用作为一条语句执行。执行这个命令的前提是已经run,开始了代码的执行。

13step命令:执行下一条源代码,进入函数内部。如果调用了某个函数,会跳到函数所在的代码中等候一步步执行。执行这个命令的前提是已经用run开始执行代码。

14display命令:在应用程序每次停止运行时显示表达式的值。

15info break命令:显示当前断点列表,包括每个断点到达的次数。

16info files命令:显示调试文件的信息。

17info func命令:显示所有的函数名。

18info local命令:显示当前函数的所有局部变量的信息

19info prog命令:显示调试程序的执行状态。

20print命令;显示表达式的值。

21delete命令:删除断点。指定一个断点号码,则删除指定断点。不指定参数则删除所有的断点。

22Shell命令:执行Linux Shell命令。

23make命令:不退出gdb而重新编译生成可执行文件。

24Quit命令:退出gdb

gcc -g -o example example1.c

gdb example

list

l

watch a监视变量a的值

r

break 3设置断点在3

quit

阅读(1124) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~