LinuxGCCC语言编辑器.docx

上传人:b****4 文档编号:5465785 上传时间:2022-12-16 格式:DOCX 页数:18 大小:32.85KB
下载 相关 举报
LinuxGCCC语言编辑器.docx_第1页
第1页 / 共18页
LinuxGCCC语言编辑器.docx_第2页
第2页 / 共18页
LinuxGCCC语言编辑器.docx_第3页
第3页 / 共18页
LinuxGCCC语言编辑器.docx_第4页
第4页 / 共18页
LinuxGCCC语言编辑器.docx_第5页
第5页 / 共18页
点击查看更多>>
下载资源
资源描述

LinuxGCCC语言编辑器.docx

《LinuxGCCC语言编辑器.docx》由会员分享,可在线阅读,更多相关《LinuxGCCC语言编辑器.docx(18页珍藏版)》请在冰豆网上搜索。

LinuxGCCC语言编辑器.docx

LinuxGCCC语言编辑器

LinuxGCC(C语言编辑器)

摘要:

要想读懂本文,你需要对C语言有基本的了解,本文将介绍如何使用gcc编译器。

首先,我们介绍如何在命令行方式下使用编译器编译简单的C源代码。

然后,我

们简要介绍一下编译器究竟作了那些工作,以及如何控制编译过程。

我们也简要

介绍了调试器的使用方法。

GCCrules

你能想象使用封闭源代码的私有编译器编译自由软件吗?

你怎么知道编译器在你

的可执行文件中加入了什么?

可能会加入各种后门和木马。

KenThompson是一个

著名的黑客,他编写了一个编译器,当编译器编译自己时,就在'login'程序中

留下后门和永久的木马。

幸运的是,我们有了gcc。

当你进行configure;make;

makeinstall时,gcc在幕后做了很多繁重的工作。

如何才能让gcc为我们工作

呢?

我们将开始编写一个纸牌游戏,不过我们只是为了演示编译器的功能,所以

尽可能地精简了代码。

我们将从头开始一步一步地做,以便理解编译过程,了解

为了制作可执行文件需要做些什么,按什么顺序做。

我们将看看如何编译C程序

,以及如何使用编译选项让gcc按照我们的要求工作。

步骤(以及所用工具)如

下:

预编译(gcc-E),编译(gcc),汇编(as),和连接(ld)。

开始...

首先,我们应该知道如何调用编译器。

实际上,这很简单。

我们将从那个著名的

第一个C程序开始。

(各位老前辈,请原谅我)。

#include

intmain()

{

 printf("HelloWorld!

\n");

}

把这个文件保存为game.c。

你可以在命令行下编译它:

gccgame.c

在默认情况下,C编译器将生成一个名为a.out的可执行文件。

你可以键入如下

命令运行它:

a.out

HelloWorld

每一次编译程序时,新的a.out将覆盖原来的程序。

你无法知道是哪个程序创

建了a.out。

我们可以通过使用-o编译选项,告诉gcc我们想把可执行文件叫

什么名字。

我们将把这个程序叫做

game,我们可以使用任何名字,因为C没有Java那样的命名限制。

gcc-ogamegame.c

game

HelloWorld

到现在为止,我们离一个有用的程序还差得很远。

如果你觉得沮丧,你可以想一

想我们已经编译并运行了一个程序。

因为我们将一点一点为这个程序添加功能,

所以我们必须保证让它能够运行。

似乎每个刚开始学编程的程序员都想一下子编

一个1000行的程序,然后一次修改所有的错误。

没有人,我是说没有人,能做到

这个。

你应该先编一个可以运行的小程序,修改它,然后再次让它运行。

这可以

限制你一次修改的错误数量。

另外,你知道刚才做了哪些修改使程序无法运行,

因此你知道应该把注意力放在哪里。

这可以防止这样的情况出现:

你认为你编写

的东西应该能够工作,它也能通过编译,但它就是不能运行。

请切记,能够通过

编译的程序并不意味着它是正确的。

下一步为我们的游戏编写一个头文件。

头文件把数据类型和函数声明集中到了一

处。

这可以保证数据结构定义的一致性,以便程序的每一部分都能以同样的方式

看待一切事情。

#ifndefDECK_H

#defineDECK_H

#defineDECKSIZE52

typedefstructdeck_t

{

 intcard[DECKSIZE];

 /*numberofcardsused*/

 intdealt;

}deck_t;

#endif/*DECK_H*/

把这个文件保存为deck.h。

只能编译.c文件,所以我们必须修改game.c。

game.c的第2行,写上#include"deck.h"。

在第5行写上deck_tdeck;。

为了

保证我们没有搞错,把它重新编译一次。

gcc-ogamegame.c

如果没有错误,就没有问题。

如果编译不能通过,那么就修改它直到能通过为止

 

预编译

编译器是怎么知道deck_t

类型是什么的呢?

因为在预编译期间,它实际上把"deck.h"文件复制到了"game.

c"文件中。

源代码中的预编译指示以"#"为前缀。

你可以通过在gcc后加上-E选

项来调用预编译器。

gcc-E-ogame_precompile.txtgame.c

wc-lgame_precompile.txt

 3199game_precompile.txt

几乎有3200行的输出!

其中大多数来自stdio.h包含文件,但是如果你查看这

个文件的话,我们的声明也在那里。

如果你不用-o选项指定输出文件名的话,

它就输出到控制台。

预编译过程通过完成三个主要任务给了代码很大的灵活性。

1.把"include"的文件拷贝到要编译的源文件中。

2.用实际值替代"define"的文本。

3.在调用宏的地方进行宏替换。

这就使你能够在整个源文件中使用符号常量(即用DECKSIZE表示一付牌中的纸牌

数量),而符号常量是在一个地方定义的,如果它的值发生了变化,所有使用符

号常量的地方都能自动更新。

在实践中,你几乎不需要单独使用-E选项,而是

让它把输出传送给编译器。

编译

作为一个中间步骤,gcc把你的代码翻译成汇编语言。

它一定要这样做,它必须

通过分析你的代码搞清楚你究竟想要做什么。

如果你犯了语法错误,它就会告诉

你,这样编译就失败了。

人们有时会把这一步误解为整个过程。

但是,实际上还

有许多工作要gcc去做呢。

汇编

as把汇编语言代码转换为目标代码。

事实上目标代码并不能在CPU上运行,但它

离完成已经很近了。

编译器选项-c把.c文件转换为以.o为扩展名的目标文

件。

如果我们运行

gcc-cgame.c

我们就自动创建了一个名为game.o的文件。

这里我们碰到了一个重要的问题。

们可以用任意一个.c文件创建一个目标文件。

正如我们在下面所看到的,在连

接步骤中我们可以把这些目标文件组合成可执行文件。

让我们继续介绍我们的例

子。

因为我们正在编写一个纸牌游戏,我们已经把一付牌定义为deck_t,我们

将编写一个洗牌函数。

这个函数接受一个指向deck类型的指针,并把一付随机的

牌装入deck类型。

它使用'drawn'

数组跟踪记录那些牌已经用过了。

这个具有DECKSIZE个元素的数组可以防止我们

重复使用一张牌。

#include

#include

#include

#include"deck.h"

statictime_tseed=0;

voidshuffle(deck_t*pdeck)

{

 /*Keepstrackofwhatnumbershavebeenused*/

 intdrawn[DECKSIZE]={0};

 inti;

 /*Onetimeinitializationofrand*/

 if(0==seed)

 {

   seed=time(NULL);

   srand(seed);

 }

 for(i=0;i

 {

   intvalue=-1;

   do

   {

     value=rand()%DECKSIZE;

   }

   while(drawn[value]!

=0);

   /*markvalueasused*/

   drawn[value]=1;

   /*debugstatement*/

   printf("%i\n",value);

   pdeck->card[i]=value;

 }

 pdeck->dealt=0;

 return;

}

把这个文件保存为shuffle.c。

我们在这个代码中加入了一条调试语句,以便运

行时,能输出所产生的牌号。

这并没有为我们的程序添加功能,但是现在到了关

键时刻,我们看看究竟发生了什么。

因为我们的游戏还在初级阶段,我们没有别

的办法确定我们的函数是否实现了我们要求的功能。

使用那条printf语句,我们

就能准确地知道现在究竟发生了什么,以便在开始下一阶段之前我们知道牌已经

洗好了。

在我们对它的工作感到满意之后,我们可以把那一行语句从代码中删掉

这种调试程序的技术看起来很粗糙,但它使用最少的语句完成了调试任务。

后我们再介绍更复杂的调试器。

请注意两个问题。

1.我们用传址方式传递参数,你可以从'&'(取地址)操作符看出来。

这把变量

的机器地址传递给了函数,因此函数自己就能改变变量的值。

也可以使用全局变

量编写程序,但是应该尽量少使用全局变量。

指针是C的一个重要组成部分,你

应该充分地理解它。

2.我们在一个新的.c文件中使用函数调用。

操作系统总是寻找名为'main'的

函数,并从那里开始执行。

shuffle.c中没有'main'函数,因此不能编译为独

立的可执行文件。

我们必须把它与另一个具有'main'函数并调用'shuffle'的程

序组合起来。

运行命令

gcc-cshuffle.c

并确定它创建了一个名为shuffle.o的新文件。

编辑game.c文件,在第7行,在

deck_t类型的变量deck声明之后,加上下面这一行:

shuffle(&deck);

现在,如果我们还象以前一样创建可执行文件,我们就会得到一个错误

gcc-ogamegame.c

/tmp/ccmiHnJX.o:

Infunction`main':

/tmp/ccmiHnJX.o(.text+0xf):

undefinedreferenceto`shuffle'

collect2:

ldreturned1exitstatus

编译成功了,因为我们的语法是正确的。

但是连接步骤却失败了,因为我们没有

告诉编译器'shuffle'函数在哪里。

那么,到底什么是连接?

我们怎样告诉编译

器到哪里寻找这个函数呢?

连接

连接器ld,使用下面的命令,接受前面由as创建的目标文件并把它转换为可执

行文件

gcc-ogamegame.oshuffle.o

这将把两个目标文件组合起来并创建可执行文件game。

连接器从shuffle.o目标文件中找到shuffle函数,并把它包括进可执行文件。

目标文件的真正好处在于,如果我们想再次使用那个函数,我们所要做的就是包

含"deck.h"文件并把shuffle.o目标文件连接到新的可执行文件中。

象这样的代码重用是经常发生的。

虽然我们并没有编写前面作为调试语句调用的

printf函数,连接器却能从我们用#include语句包含的文件中找

到它的声明,并把存储在C库(/lib/libc.so.6)中的目标代码连接进来。

这种

方式使我们可以使用已能正确工作的其他人的函数,只关心我们所要解决的问题

这就是为什么头文件中一般只含有数据和函数声明,而没有函数体。

一般,你

可以为连接器创建目标文件或函数库,以便连接进可执行文件。

我们的代码可能

产生问题,因为在头文件中我们没有放入任何函数声明。

为了确保一切顺利,我

们还能做什么呢?

 

另外两个重要选项

-Wall选项可以打开所有类型的语法警告,以便帮助我们确定代码是正确的,并

且尽可能实现可移植性。

当我们使用这个选项编译我们的代码时,我们将看到下

述警告:

game.c:

9:

warning:

implicitdeclarationoffunction`shuffle'

这让我们知道还有一些工作要做。

我们需要在头文件中加入一行代码,以便告诉

编译器有关shuffle函数的一切,让它可以做必要的检查。

听起来象是一种狡

辩,但这样做可以把函数的定义与实现分离开来,使我们能在任何地方使用我

们的函数,只要包含新的头文件并把它连接到我们的目标文件中就可以了。

面我们就把这一行加入deck.h中。

voidshuffle(deck_t*pdeck);

这就可以消除那个警告信息了。

另一个常用编译器选项是优化选项-O#(即-O2)。

这是告诉编译器你需要什么

级别的优化。

编译器具有一整套技巧可以使你的代码运行得更快一点。

对于象我

们这种小程序,你可能注意不到差别,但对于大型程序来说,它可以大幅度提高

运行速度。

你会经常碰到它,所以你应该知道它的意思。

 

调试

  我们都知道,代码通过了编译并不意味着它按我们得要求工作了。

你可以使

用下面的命令验证是否所有的号码都被使用了

game|sort-n|less

  并且检查有没有遗漏。

如果有问题我们该怎么办?

我们如何才能深入底层查

找错误呢?

你可以使用调试器检查你的代码。

大多数发行版都提供著名的调试器:

gdb。

果那些众多的命令行选项让你感到无所适从,那么你可以使用KDE提供的一个很

好的前端工具KDbg。

还有一些其它的前端工具,它们都很相似。

要开始调试,

你可以选择File->Executable然后找到你的game程序。

当你按下F5键或选择

Execution->从菜单运行时,你可以在另一个窗口中看到输出。

怎么回事?

在那

个窗口中我们什么也看不到。

不要担心,KDbg没有出问题。

问题在于我们在可执

行文件中没有加入任何调试信息,所以KDbg不能告诉我们内部发生了什么。

编译

器选项-g可以把必要的调试信息加入目标文件。

你必须用这个选项编译目标文

件(扩展名为.o),所以命令行成了:

  gcc-g-cshuffle.cgame.c

  gcc-g-ogamegame.oshuffle.o

  这就把钩子放入了可执行文件,使gdb和KDbg能指出运行情况。

调试是一种

很重要的技术,很值得你花时间学习如何使用。

调试器帮助程序员的方法是它能

在源代码中设置“断点”。

现在你可以用右键单击调用shuffle函数的那行代

码,试着设置断点。

那一行边上会出现一个红色的小圆圈。

现在当你按下F5键时

,程序就会在那一行停止执行。

按F8可以跳入shuffle函数。

呵,我们现在可以

看到shuffle.c中的代码了!

我们可以控制程序一步一步地执行,并看到究竟

发生了什么事。

如果你把光标暂停在局部变量上,你将能看到变量的内容。

太好

了。

这比那条printf语句好多了,是不是?

    小结

  本文大体介绍了编译和调试C程序的方法。

我们讨论了编译器走过的步骤,

以及为了让编译器做这些工作应该给gcc传递哪些选项。

我们简述了有关连接共

享函数库的问题,最后介绍了调试器。

真正了解你所从事的工作还需要付出许多

努力,但我希望本文能让你正确地起步。

你可以在gcc、as和ld的man和

infopage中找到更多的信息。

  自己编写代码可以让你学到更多的东西。

作为练习你可以以本文的纸牌游戏

为基础,编写一个21点游戏。

那时你可以学学如何使用调试器。

使用GUI的KDbg

开始可以更容易一些。

如果你每次只加入一点点功能,那么很快就能完成。

切记

,一定要保持程序一直能运行!

  要想编写一个完整的游戏,你需要下面这些内容:

*一个纸牌玩家的定义(即,你可以把deck_t定义为player_t)。

*一个给指定玩家发一定数量牌的函数。

记住在纸牌中要增加“已发牌”的数量,以便能知道还有那些牌可发。

还要记住玩家手中还有多少牌。

*一些与用户的交互,问问玩家是否还要另一张牌。

*一个能打印玩家手中的牌的函数。

card等于value%13(得数为0到12),

suit等于value/13(得数为0到3)。

*

一个能确定玩家手中的value的函数。

Ace的value为零并且可以等于1或11。

King

的value为12并且可以等于10。

 

 GCC精彩之旅[zz]

为Linux开发应用程序时,绝大多数情况下使用的都是C语言,因此几乎每一位Linux程序员面临的首要问题都是如何灵活运用C编译器。

目前Linux下最常用的C语言编译器是GCC(GNUCompilerCollection),它是GNU项目中符合ANSIC标准的编译系统,能够编译用C、C++和ObjectC等语言编写的程序。

GCC不仅功能非常强大,结构也异常灵活。

最值得称道的一点就是它可以通过不同的前端模块来支持各种语言,如Java、Fortran、Pascal、Modula-3和Ada等。

开放、自由和灵活是Linux的魅力所在,而这一点在GCC上的体现就是程序员通过它能够更好地控制整个编译过程。

在使用GCC编译程序时,编译过程可以被细分为四个阶段:

◆预处理(Pre-Processing)

◆编译(Compiling)

◆汇编(Assembling)

◆链接(Linking)

Linux程序员可以根据自己的需要让GCC在编译的任何阶段结束,以便检查或使用编译器在该阶段的输出信息,或者对最后生成的二进制文件进行控制,以便通过加入不同数量和种类的调试代码来为今后的调试做好准备。

和其它常用的编译器一样,GCC也提供了灵活而强大的代码优化功能,利用它可以生成执行效率更高的代码。

GCC提供了30多条警告信息和三个警告级别,使用它们有助于增强程序的稳定性和可移植性。

此外,GCC还对标准的C和C++语言进行了大量的扩展,提高程序的执行效率,有助于编译器进行代码优化,能够减轻编程的工作量。

GCC起步

在学习使用GCC之前,下面的这个例子能够帮助用户迅速理解GCC的工作原理,并将其立即运用到实际的项目开发中去。

首先用熟悉的编辑器输入清单1所示的代码:

清单1:

hello.c

#include

intmain(void)

{

printf("Helloworld,Linuxprogramming!

n");

return0;

}

然后执行下面的命令编译和运行这段程序:

#gcchello.c-ohello

#./hello

Helloworld,Linuxprogramming!

从程序员的角度看,只需简单地执行一条GCC命令就可以了,但从编译器的角度来看,却需要完成一系列非常繁杂的工作。

首先,GCC需要调用预处理程序cpp,由它负责展开在源文件中定义的宏,并向其中插入“#include”语句所包含的内容;接着,GCC会调用ccl和as将处理后的源代码编译成目标代码;最后,GCC会调用链接程序ld,把生成的目标代码链接成一个可执行程序。

为了更好地理解GCC的工作过程,可以把上述编译过程分成几个步骤单独进行,并观察每步的运行结果。

第一步是进行预编译,使用-E参数可以让GCC在预处理结束后停止编译过程:

#  gcc-Ehello.c-ohello.i

此时若查看hello.cpp文件中的内容,会发现stdio.h的内容确实都插到文件里去了,而其它应当被预处理的宏定义也都做了相应的处理。

下一步是将hello.i编译为目标代码,这可以通过使用-c参数来完成:

#  gcc-chello.i-ohello.o

GCC默认将.i文件看成是预处理后的C语言源代码,因此上述命令将自动跳过预处理步骤而开始执行编译过程,也可以使用-x参数让GCC从指定的步骤开始编译。

最后一步是将生成的目标文件链接成可执行文件:

#  gcchello.o-ohello

在采用模块化的设计思想进行软件开发时,通常整个程序是由多个源文件组成的,相应地也就形成了多个编译单元,使用GCC能够很好地管理这些编译单元。

假设有一个由foo1.c和foo2.c两个源文件组成的程序,为了对它们进行编译,并最终生成可执行程序foo,可以使用下面这条命令:

#  gccfoo1.cfoo2.c-ofoo

如果同时处理的文件不止一个,GCC仍然会按照预处理、编译和链接的过程依次进行。

如果深究起来,上面这条命令大致相当于依次执行如下三条命令:

#gcc-cfoo1.c-ofoo1.o

#gcc-cfoo2.c-ofoo2.o

#gccfoo1.ofoo2.o-ofoo

在编译一个包含许多源文件的工程时,若只用一条GCC命令来完成编译是非常浪费时间的。

假设项目中有100个源文件需要编译,并且每个源文件中都包含10000行代码,如果像上面那样仅用一条GCC命令来完成编译工作,那么GCC需要将每个源文件都重新编译一遍,然后再全部连接起来。

很显然,这样浪费的时间相当多,尤其是当用户只是修改了其中某一个文件的时候,完全没有必要将每个文件都重新编译一遍,因为很多已经生成的目标文件是不会改变的。

要解决这个问题,关键是要灵活运用GCC,同时还要借助像Make这样的工具。

警告提示功能

GCC包含完整的出错检查和警告提示功能,它们可以帮助Linux程序员写出更加专业和优美的代码。

先来读读清单2所示的程序,这段代码写得很糟糕,仔细检查一下不难挑出很多毛病:

◆main函数的返回值被声明为void,但实际上应该是int;

◆使用了GNU语法扩展,即使用longlong来声明64位整数,不符合ANSI/ISOC语言标准;

◆main函数在终止前没有调用return语句。

清单2:

illcode.c

#include

voidmain(void)

{

  longlongintvar=1;

  printf("ItisnotstandardCcode!

n");

}

下面来看看GCC是如何帮助程序员来发现这些错误的。

当GCC在编译不符合ANSI/ISOC语言标准的源代码时,如果加上了-pedantic选项,那么使用了扩展语法的地方将产生相应的警告信息:

#gcc-pedanticillcode.c-oillcode

illcode.c:

Infunction`main':

illcode.c:

9:

ISOC89doesnotsupport`longlong'

illcode.c:

8:

returntypeof`main'isnot`int'

需要注意的是,-pedantic编译选项并不能保证被编译程序与ANSI/ISOC标准的完全兼容,它仅仅只能用来帮助Linux程序员离这个目标越来越近。

或者换句话说,-pedantic选项能够帮助程序员发现一些不符合ANSI/ISOC标准的代码,但不是全部,事实上只有ANSI/ISOC语言标准中要求进行编译器诊断的那些情况,才有可能被GCC发现并提出警告。

除了-p

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 解决方案 > 学习计划

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1