当前位置:文档之家› GNU C 编程

GNU C 编程

GNU C 编程
GNU C 编程

Linux/Unix下C编程简介

GNU 标准及GNU C简介

C语言编程简述

Linux/Unix下的c编程

Linux/Unix下创建工程

基于GNU C标准的Tornado 编程

GNU 标准及GNU C简介

1、GUN是什么?

GNU是GNU‘s not UNIX的缩写。GNU项目是1984年由Stallman提出的,目的是开发完全自由(free)的Unix操作系统。这里的free指的是自由,而不是免费,你可以自由地运行,复制、发布、研究、修改和改进软件。

GUN的核心思想是:软件应该自由,用户的自由值得保护。如果人们无法珍惜自由,则自由就不可能长久。为了让自由更长久,我们要教会人们珍惜自由。

GNU项目的方法是,自由软件和用户的自上思想是相互支持的。我们开发GNU 软件,人们遇到和开始使用GNU程序或GNU系统时,也要按GNU的思想思考。这个软件证明这种思想是可行的。人们认同这种思想之后,又会编写出更多的自由软件。这样软件体现这种思想,传播这种思想,并从这种思想中成长。

2、GNU C 编译器GCC

我们在学习一些基础的时候,通常会说在学习“ABC”,那么,我们可以说,今天学习的基础是这个“ABC”中最后一步了。有没有A语言我就不得而知了,当的确有一种叫做B的语言,而C语言正是从B语言发展而来的。发展到今天,C语言有许多版本,常见的有Microsoft C、Turbo C、Quick C等。面向对象的概念出现之后,C语言吸收了这个新概念,形成了C++语言。近年来,出现了可视化编程工具Visual C/C++、Borland C++ Builder。

GCC(The GNU C Compiler)是Linux/Unix下最常用ANSI C的C/C++编译器。

后来一度改名为EGCS(Experimental/Enhanced GNU Compiler System)。最后又改名为GCC,不过其含义有所变化,这时GCC被解释为GNU Compiler Collection,因为这时它不仅能编译C,还可以编译FORTRAN语言程序。其它编译调试工具还有G++ : GNU 提供的C++编译器、PGCC: Pentium GCC等等。实用的调方式工具如gdb/xxgdb。

基于GNU C 的集成编译器,比较有名的有KDevelop,KDevelop是KDE下的一个集成编译环境。其外观和使用都和VC++比较相似,功能也非常强大,还可以编写基于X Window的应用程序,虽然只提供了较简单的资源设计功能,但已经可以满足大多数窗口应用程序开发的需要。另外,它附有大量的开发文档,使用非常方便。在一些中文环境的Linux系统中,甚至提供了中文文档。

另一个比较好的C/C++编译器是rhide,这个集成编译环境做的非常象Borland C++3集成编译环境,使用起来很顺手。由于集成了编译和调试环境,不仅可以进行代码编辑、程序编译、链接,还可以进行断点调试,单步调试等。

C 语言编程简述

1、 C 的数据类型

程序 = 数据结构 + 算法

评价一个程序语言的优劣,很重要一点就是看它所支持的数据类型。C 语言提供了极为丰富的数据类型。

2、 表达式

算术运算符: + , - , * , / , % , ++ , --

关系运算符: < , > , == , <= ,=> , !=

逻辑运算符: && , || , !

位运算符 : & , ~ , | , << , >>

赋值运算符: = 以及等的扩展运算符

指针运算符: * , &

条件运算符: ? :

说到运算符,不能不提(强制)类型转换。强制类型转换是将一种类型的变量转换为另一种类型的运算符。这是一定会遇到的,一个很常见的例子就是用 malloc 、calloc 进行内存分配的时候,几乎一定要做强制类型转换。如:

C语言规定某些情况下数据类型自动转换。

基本类型

整型 布尔型 实型 字符型 构造类型 数组 指针 枚举型

联合 空(NULL) 结构

图1 C 语言数据类型 char *ret;

ret = (char *)malloc (MAX_LEN); Double Long (Unsigned) int short Float

Char 图二 C 语言类型自动转换图示

3、控制语句

三种基本程序结构: 顺序结构、选择结构、循环结构

主要的流程控制语句包括:if-else 、switch、for 、while、continue、break、goto。

4、函数

C语言是面向过程的语言,从C发展而来的C++中加入了类的概念,成为面向对象的语言,但其中也保留了一些模块化的东西。因些,过程-在C语言中称之为函数是C 中的一个很重要的概念。

函数定义的一般形式为:

类型标识符函数名(形式参数列表)

{

函数体

}

5、宏

所谓宏,就是用一个指定的标识符来代表一个字符串。这个指定的标识符称为宏名。宏定义的格式为:

#define 标识符字符串

宏展开。在对程序进行通常的编译之前,“编译预处理器”先将程序中出现的宏用它代表的字符串替换,然后将替换的结果作为一个整体进行编译。对宏进行的这种处理的过程叫做宏展开。

如果你决意要看一下宏展开是什么样子的话,不是没有可能的:

创建一个简单的包含宏的程序(简单到没有头文件)

然后用下面的命令进行宏展开预处理,就可以看到预处理后的输出结果:$ gcc –E test.c

Linux/Unix下的c编程

Gcc的编译选项之多,是前无古人的 ,所以我不打算,也没有那个本事给大家讲所有的参数。不过,我可以通过一个简单的例子来介绍如何在Linux下编写自已的程序。

好了,不论用什么编译器,先编一段程序再说。例如:mp26. c。第一个想法,应该是如何让程序跑起来,不管它的运行结果正不正确。那们我们就开始gcc的第一个用法:$gcc mp26.c

如果成功的话,这时,在你的目录下就会出现一个叫做a.out的文件。这就是我们编译出来的可执行文件,你可以运行它。

$./a.out

这里有一个执得大家注意的问题,特别是不是很熟悉Linux/Unix的同志。在Linux/Unix 下,运行一个程序,如果程序不是在系统路径下,必要指定其目录才能运行,即使文件就在当前目录也不例外。要查看系统路径(PA TH)信息,可以用env命令或echo $PATH。

使用 GCC

通常后跟一些选项和文件名来使用 GCC 编译器. gcc 命令的基本用法如下: gcc [options] [filenames]

GCC常用的编译选项:

1、-c生成目标代码(object file),但不键接。

用法:gcc –c

如gcc –c mp26.c 则会生成mp26.o ,但是并不产生可执行的文件。

2、-o生成可执行文件。

用法:gcc –o

这里要小心,因为在在某一些文档在介绍的时候,将前面Executable即输出文件说明为可选参数。但在许多版本中,这个参数都是必选的。这样如果你不少心省

略了前一个参数,则这个编译的后果就是丢失你的源文件,所以一定要非常小心。

3、-g这应该说是一个非常有用的参数,它的作用是在生成的目标代码中包含调试信

息,如果你试图用gdb/xxgdb对可执行代码进行调试的话,那么就一定要用到这个

选项。否则,你在gdb中进行单步调试,断点设置等都会失败。

如:gcc –g –o mp26 mp26.c

4、-S 这个参数使编译器对源文件进行汇编。可以看到编译结果是生成了一个以s为

扩展名的文件。这是一个汇编程序源代码。

如:gcc –S mp26.c

5、-E 进行预编译宏展开和文件包含。

6、-Wall 显示附加的警告信息。

7、-O 进行优化。还可以用–O2,-O3来选用更高级的优化。

8、-pedantic 严格要求符合ANSI标准。

out格式与ELF格式

从前面的例子,我们看到,如果不加参数,用gcc 编译之后得到的是一个叫a.out的可执行文件。实际上,a.out这个名词还对应了一种文件格式,即Linux过去使用的老的二进制格式。ELF 是Executable and Linking Format 的缩写,最初是由USL(Unix System

Laboratories)发展而来的另一种二进制格式,目前正应用于Solaris与System V Release4以及后来的许多系统上。

ELF主要优点:增长弹性远远超过Linux过去用的a.out格式,并且ELF在设置共享程序库上有更大的便利性。1995年,GCC与C程序库的发展人士决定改用ELF作为Linux标准的二进制格式。

Linux/Unix下创建工程

在Linux下何创建一个工程。这个问题,如果在现在来回答,那么我可以很简单的告诉大家,你大可以去用Kdevelop或Rhide这样的集成编译环境来轻松的创建工程。你可能完全不知道底层的C编译器是如何工作的。但是如果你想深入的去了解这些的话,就有必要了解一些在Linux下进行工程创建的问题。

现在先让我们来看一下别人做的工程是如何安装到我们的机器的。以安装Apache服务器为例:

第一步:$configure –prefix=/usr/local/apache –enable-ssl

第二步:$make

第三步:$make install

可以说,这三个步骤没有什么困难的,这是因为Apache Project 已经为我们做好了许多的准备工作,所以我们只需要用到一些简单的命令就可以把这些Source Files进行编译并且正确的安装到我们的系统上。

在这里,起了重要作用的是make 命令和工程目录下的Makefile文件。说到工程的制作,Makefile可以说是功不可漠的。Make 命令的作用简单的说是根椐makefile设置的规则对工程文件进行编译和链接。

Make 命令的一般格式是:

Make[target]

使用make比手工方式有许多的优点,一个显然的优点就是,你不用每次都敲长长的命令行,而且在对工程文件进行编译进,make可以做到“不多、不重、不漏”。不多是指make 命令只更新工程中需要更新的文件,而不去动那些没有改动过的文件。不重是指即使一个文件的多个依赖文件变化,这个文件也只更新一次。不漏是指,make不会漏掉任何一个需要更新的文件。

模块依赖关系图

GNU Make 的主要工作是读进一个文本文件, makefile 。这个文 件里主要是有关哪些文件(‘target ’目的文件)是从哪些别的 文件(‘dependencies ’依靠文件)中产生的,用什么命令来进行 这个产生过程。有了这些信息, make 会检查磁碟上的文件,如果 目的文件的时间戳(该文件生成或被改动时的时间)比至少它的一 个依靠文件旧的话, make 就执行相应的命令,以便更新目的文件。 (目的文件不一定是最后的可执行档,它可以是任何一个文件。)

makefile 一般被叫做“makefile ”或“Makefile ”。当然你可以 在 make 的命令行指定别的文件名。如果你不特别指定,它会寻 找“makefile ”或“Makefile ”,因此通常都是使用这两个名字。

一个 makefile 主要含有一系列的规则,如下:

Targets [attributes] ruleop [prerequisites] [;recipe]

{ recipe}

例如:

这是一个非常基本的 makefile —— make 从最上面开始,把上 面第一个目的,?myprog‘,做为它的主要目标(一个它需要保 证其总是最新的最终目标)。给出的规则说明只要文件?myprog‘ 比文件?foo.o‘或?bar.o‘中的任何一个旧,下一行的命令将 会被执行。

但是,在检查文件 foo.o 和 bar.o 的时间戳之前,它会往下查 找那些把 foo.o 或 bar.o 做为目标文件的规则。它找到的关于 foo.o 的规则,该文件的依靠文件是 foo.c, foo.h 和 bar.h 。 它从下面再找不到生成这些依靠文件的规则,它就开始检查磁碟 上这些依靠文件的时间戳。如果这些文件中任何一个的时间戳比 foo.o 的新,命令 'gcc -o foo.o foo.c' 将会执行,从而更新 文件 foo.o 。

接下来对文件 bar.o 做类似的检查,依靠文件在这里是文件 bar.c 和 bar.h 。

现在, make 回到?myprog‘的规则。如果刚才两个规则中的任 何一个被执行,myprog 就需要重建(因为其中一个 .o 档就会比 ?myprog‘新),因此连接命令将被执行。

最明显的(也是最简单的)编写规则的方法是一个一个的查看源码文件,把它们的目标文件做为目的,而C源码文件和被它 #include 的 header 档做为依靠文件。但是你也要把其它被这些 header 档 #include 的 header 档也列为依靠文件,还有那些被 包括的文件所包括的文件……然后你会发现要对越来越多的文件进行管理,你的脸色变成菜色,你的脾气开始变坏,你走在路上开始跟电线杆子碰撞,终于你捣毁你的电脑显示器,停止编程。到低有没有些容易点儿的方法呢?

当然有!向编译器要!在编译每一个源码文件的时候,

它实在应该知道应该包括什么样

的 header 档。使用 gcc 的时候,用 -M 开关,它会为每一个你给它的C文件输出一个规则,把目标文件做为目的,而这个C文件和所有应该被 #include 的 header 文 件将做为依靠文件。注意这个规则会加入所有 header 文件,包 括被角括号(`<', `>')和双引号(`"')所包围的文件。其实我们可以相当肯定系统 header 档(比如 stdio.h, stdlib.h 等等)不会 被我们更改,如果你用 -MM 来代替 -M 传递给 gcc ,那些用角括号包围的 header 档将不会被包括。(这会节省一些编译时间)

Makefile 变量

上面提到 makefile 里主要包含一些规则。它们包含的其它的东西是变量定义。

makefile 里的变量就像一个环境变量(environment variable)。 事实上,环境变量在 make 过程中被解释成 make 的变量。这些 变量是大小写敏感的,一般使用大写字母。它们可以从几乎任何 地方被引用,也可以被用来做很多事情,比如:

1、 贮存一个文件名列表。在上面的例子里,生成可执行文件的 规则包含一些目标文

件名做为依靠。在这个规则的命令行 里同样的那些文件被输送给 gcc 做为命令参数。如果在这 里使用一个变数来贮存所有的目标文件名,加入新的目标 文件会变的简单而且较不易出错。

2、 贮存可执行文件名。如果你的项目被用在一个非 gcc 的系 统里,或者如果你想使

用一个不同的编译器,你必须将所 有使用编译器的地方改成用新的编译器名。但是如果使用一 个变量来代替编译器名,那么你只需要改变一个地方,其 它所有地方的命令名就都改变了。

3、 贮存编译器旗标。假设你想给你所有的编译命令传递一组 相同的选项(例如 -Wall

-O -g );如果你把这组选项存 入一个变量,那么你可以把这个变量放在所有呼叫编译器 的地方。而当你要改变选项的时候,你只需在一个地方改 变这个变量的内容。

要设定一个变量,你只要在一行的开始写下这个变量的名字,后 面跟一个 = 号,后面跟你要设定的这个变量的值。以后你要引用 这个变量,写一个 $ 符号,后面是围在括号里的变量名。比如在 下面,我们把前面的 makefile 利用变量重写一遍:

还有一些设定好的内部变量,它们根据每一个规则内容定义。几个比较有用的变量是 $*,$?,$@, $< 和 $^ (这些变量不需要括号括住)。 $@ 扩展成当前规则的目的文件名,

$<

扩展成依靠列表中的第一个依靠文件,而$^ 扩展成整个依靠的列表(除掉了里面所有重复的文件名)。$?比目标文件(target)新的dependent file.而$?的值只有在使用外显示(explicit)的规则时才会被设定.$*是内存dependent file的文件名,不含扩展名. 利用这些变量,我们可以把上面的makefile 写成:

你可以用变量做许多其它的事情,特别是当你把它们和函数混合使用的时候。如果需要更进一步的了解,请参考GNU Make 手册。('man make', 'man makefile') 假设你的一个项目最后需要产生两个可执行文件。你的主要目标是产生两个可执行文件,但这两个文件是相互独立的——如果一个文件需要重建,并不影响另一个。你可以使用“假象目的”来达到这种效果。一个假象目的跟一个正常的目的几乎是一样的,只是这个目的文件是不存在的。因此,make 总是会假设它需要被生成,当把它的依赖文件更新后,就会执行它的规则里的命令行。

如果在我们的makefile 开始处输入:

all : exec1 exec2

其中exec1 和exec2 是我们做为目的的两个可执行文件。make 把这个'all' 做为它的主要目的,每次执行时都会尝试把'all' 更新。但既然这行规则里没有哪个命令来作用在一个叫'all' 的实际文件(事实上all 并不会在磁碟上实际产生),所以这个规则并不真的改变'all' 的状态。可既然这个文件并不存在,所以make 会尝试更新all 规则,因此就检查它的依靠exec1, exec2 是否需要更新,如果需要,就把它们更新,从而达到我们的目的。

假象目的也可以用来描述一组非预设的动作。例如,你想把所有由make 产生的文件删除,你可以在makefile 里设立这样一个规则:

veryclean :

rm *.o

rm myprog

前提是没有其它的规则依靠这个'veryclean' 目的,它将永远不会被执行。但是,如果你明确的使用命令'make veryclean' ,make 会把这个目的做为它的主要目标,执行那些rm 命令。

如果你的磁碟上存在一个叫veryclean 文件,会发生什么事?这时因为在这个规则里没有任何依靠文件,所以这个目的文件一定是最新的了(所有的依靠文件都已经是最新的了),所以既使用户明确命令make 重新产生它,也不会有任何事情发生。解决方法是标明所有的假象目的(用.PHONY),这就告诉make 不用检查它们是否存在于磁碟上,也不用查找任何隐含规则,直接假设指定的目的需要被更新。在makefile 里加入下面这行包含上面规则的规则:

.PHONY : veryclean

就可以了。注意,这是一个特殊的make 规则,make 知道.PHONY 是一个特殊目的,当然你可以在它的依靠里加入你想用的任何假象目的,而make 知道它们都是假象目的。

工程中重复定义的问题

如果在一个C源码文件中#include 两个档a.h 和 b.h,而 a.h 又#include 了b.h 档(原因是 b.h 档定义了一些 a.h 需要的类型),会发生什么事呢?这时该C源码文件#include 了b.h 两次。因此每一个在b.h 中的#define 都发生了两次,每一个声明发生了两次,等等。理论上,因为它们是完全一样的拷贝,所以应该不会有什么问题,但在实际应用上,这是不符合C的语法的,可能在编译时出现错误,或至少是警告。

解决的方法是要确定每一个header 档在任一个源码文件中只被包含了一次。我们一般是用预处理器来达到这个目的的。当我们进入每一个header 档时,我们为这个header 档#define 一个巨集指令。只有在这个巨集指令没有被定义的前提下,我们才真正使用该header 档的主体。在实际应用上,我们只要简单的把下面一段码放在每一个header 档的开始部分:

#ifndef FILENAME_H

#define FILENAME_H

然后把下面一行码放在最后:

#endif

用header 档的档名(大写的)代替上面的FILENAME_H,用底线代替档名中的点。有些人喜欢在#endif 加上注释来提醒他们这个#endif 指的是什么。例如:#endif /* #ifndef FILENAME_H */

你只需要在那些有编译错误的header 档中加入这个技巧,但在所有的header 档中都加入也没什么损失,到底这是个好习惯。

基于GNU C标准的Tornado 编程

作为一个结束,我想简单介绍一个基于mp2600的程序(*这里程序不再是一个合理的说法。)

在mp2600上注册自已的Shell的实例:

1、创建自已的工程。

a)File -> New project

b)选择Create downloadable application module for vxWorks (图t-1)

图t-1

c) 出现如下所示按要求填入工程名和目录,然后点击Next;

d) 在下面的步骤中,要注意一个问题:

如上面所示,A toolchain 选项应选择ppc860gnu。

之后,一路next下去,工程就创建好了。

2、在Tornado 的WorkSpace窗口中选择Builds页,并选中我们的工程,展开后,可

以看到有一个叫ppc860的子树,双击,弹出如下属性页:

在Rules选项页选则Rule为archive. Macros页中的Macros也选择ACHIVE;

将Value的值改为工程库libPPC860gnuvx.a,即表明我们的工程编译后加入到这个库中去。这样我们才可能有效的在工程中加入自已的代码。

3、创建工程文件()

int mycommand(int argc,char**argv)

{

printf(―My command is running!\n‖);

}

void regMyTestCmd(void)

{

cmd_register(MyCom,"mycommand(EN,0, 'My test command')");

}

之后,在workspace中找到工程mp2600,其中有一个叫做mprShell.c的文件,这个文件完成Shell命令的注册。在这个文件中找到

void mprCommandRegister(void)

在这个函数中,我们可以看到各个26工作组所注册的Shell都由这里注册在系统。在适当位置加入我们的注册命令。RegMyTestCmd(); 如果仅仅是这样,你可能会得到一条出错信息,表示找不到regMyTestCmd函数。可以有许多办法解决这个问题。一个简单的办法就是在mprShell的前面加上:

extern void regMyTestCmd(void);

注意:请不要在这个时候提交你的文件到公司的CVS服务器上。这会给其他人造成麻烦,当然,你会有更多的麻烦。

这段程序将在Mp2600的enable模式下注册一个叫做MyCom的Shell命令。我们可以在enable模式下用show命令看到,的确有一个叫MyCom的命令,其命令说明为“My test command‖.

在enable模式下输入MyCom,可以看到命令输出:

My command is running!

Redice li

2001年4月21日星期六

相关主题
文本预览
相关文档 最新文档