如何用gcc编译.docx

上传人:b****6 文档编号:5890130 上传时间:2023-01-01 格式:DOCX 页数:8 大小:21.32KB
下载 相关 举报
如何用gcc编译.docx_第1页
第1页 / 共8页
如何用gcc编译.docx_第2页
第2页 / 共8页
如何用gcc编译.docx_第3页
第3页 / 共8页
如何用gcc编译.docx_第4页
第4页 / 共8页
如何用gcc编译.docx_第5页
第5页 / 共8页
点击查看更多>>
下载资源
资源描述

如何用gcc编译.docx

《如何用gcc编译.docx》由会员分享,可在线阅读,更多相关《如何用gcc编译.docx(8页珍藏版)》请在冰豆网上搜索。

如何用gcc编译.docx

如何用gcc编译

目录:

∙GCCrules

∙开始...

∙预编译

∙编译

∙汇编

∙连接

∙另外两个重要选项

∙调试

∙小结

∙站点链接

 

摘要:

要想读懂本文,你需要对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。

 

站点链接

∙gccGCCGNUCompilerCollection

∙gdbGNUDebugger

∙KDbgKDE'sGUIDebugger

∙AwardWinningCompilerHackKenThompson'sgreatcompilerhack

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

当前位置:首页 > 自然科学

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

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