gcc编译器是什么运行不了。

编译器的工作过程 - 阮一峰的网络日志
编译器的工作过程
源码要运行,必须先转成二进制的机器码。这是编译器的任务。
比如,下面这段源码(假定文件名叫做test.c)。
#include &stdio.h&
int main(void)
fputs("Hello, world!\n", stdout);
要先用编译器处理一下,才能运行。
$ gcc test.c
Hello, world!
对于复杂的项目,编译过程还必须分成三步。
$ ./configure
$ make install
这些命令到底在干什么?大多数的书籍和资料,都语焉不详,只说这样就可以编译了,没有进一步的解释。
本文将介绍编译器的工作过程,也就是上面这三个命令各自的任务。我主要参考了Alex Smith的文章。需要声明的是,本文主要针对gcc编译器,也就是针对C和C++,不一定适用于其他语言的编译。
第一步 配置(configure)
编译器在开始工作之前,需要知道当前的系统环境,比如标准库在哪里、软件的安装位置在哪里、需要安装哪些组件等等。这是因为不同计算机的系统环境不一样,通过指定编译参数,编译器就可以灵活适应环境,编译出各种环境都能运行的机器码。这个确定编译参数的步骤,就叫做"配置"(configure)。
这些配置信息保存在一个配置文件之中,约定俗成是一个叫做configure的脚本文件。通常它是由工具生成的。编译器通过运行这个脚本,获知编译参数。
configure脚本已经尽量考虑到不同系统的差异,并且对各种编译参数给出了默认值。如果用户的系统环境比较特别,或者有一些特定的需求,就需要手动向configure脚本提供编译参数。
$ ./configure --prefix=/www --with-mysql
上面代码是php源码的一种编译配置,用户指定安装后的文件保存在www目录,并且编译时加入mysql模块的支持。
第二步 确定标准库和头文件的位置
源码肯定会用到标准库函数(standard library)和头文件(header)。它们可以存放在系统的任意目录中,编译器实际上没办法自动检测它们的位置,只有通过配置文件才能知道。
编译的第二步,就是从配置文件中知道标准库和头文件的位置。一般来说,配置文件会给出一个清单,列出几个具体的目录。等到编译时,编译器就按顺序到这几个目录中,寻找目标。
第三步 确定依赖关系
对于大型项目来说,源码文件之间往往存在依赖关系,编译器需要确定编译的先后顺序。假定A文件依赖于B文件,编译器应该保证做到下面两点。
(1)只有在B文件编译完成后,才开始编译A文件。
(2)当B文件发生变化时,A文件会被重新编译。
编译顺序保存在一个叫做makefile的文件中,里面列出哪个文件先编译,哪个文件后编译。而makefile文件由configure脚本运行生成,这就是为什么编译时configure必须首先运行的原因。
在确定依赖关系的同时,编译器也确定了,编译时会用到哪些头文件。
第四步 头文件的预编译(precompilation)
不同的源码文件,可能引用同一个头文件(比如stdio.h)。编译的时候,头文件也必须一起编译。为了节省时间,编译器会在编译源码之前,先编译头文件。这保证了头文件只需编译一次,不必每次用到的时候,都重新编译了。
不过,并不是头文件的所有内容,都会被预编译。用来声明宏的#define命令,就不会被预编译。
第五步 预处理(Preprocessing)
预编译完成后,编译器就开始替换掉源码中bash的头文件和宏。以本文开头的那段源码为例,它包含头文件stdio.h,替换后的样子如下。
extern int fputs(const char *, FILE *);
extern FILE *
int main(void)
fputs("Hello, world!\n", stdout);
为了便于阅读,上面代码只截取了头文件中与源码相关的那部分,即fputs和FILE的声明,省略了stdio.h的其他部分(因为它们非常长)。另外,上面代码的头文件没有经过预编译,而实际上,插入源码的是预编译后的结果。编译器在这一步还会移除注释。
这一步称为"预处理"(Preprocessing),因为完成之后,就要开始真正的处理了。
第六步 编译(Compilation)
预处理之后,编译器就开始生成机器码。对于某些编译器来说,还存在一个中间步骤,会先把源码转为汇编码(assembly),然后再把汇编码转为机器码。
下面是本文开头的那段源码转成的汇编码。
.string "Hello, world!\n"
main, @function
.cfi_startproc
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
%rsp, %rbp
.cfi_def_cfa_register 6
stdout(%rip), %rax
%rax, %rcx
$.LC0, %edi
.cfi_def_cfa 7, 8
.cfi_endproc
main, .-main
"GCC: (Debian 4.9.1-19) 4.9.1"
.note.GNU-stack,"",@progbits
这种转码后的文件称为对象文件(object file)。
第七步 连接(Linking)
对象文件还不能运行,必须进一步转成可执行文件。如果你仔细看上一步的转码结果,会发现其中引用了stdout函数和fwrite函数。也就是说,程序要正常运行,除了上面的代码以外,还必须有stdout和fwrite这两个函数的代码,它们是由C语言的标准库提供的。
编译器的下一步工作,就是把外部函数的代码(通常是后缀名为.lib和.a的文件),添加到可执行文件中。这就叫做连接(linking)。这种通过拷贝,将外部函数库添加到可执行文件的方式,叫做(static linking),后文会提到还有(dynamic linking)。
make命令的作用,就是从第四步头文件预编译开始,一直到做完这一步。
第八步 安装(Installation)
上一步的连接是在内存中进行的,即编译器在内存中生成了可执行文件。下一步,必须将可执行文件保存到用户事先指定的安装目录。
表面上,这一步很简单,就是将可执行文件(连带相关的数据文件)拷贝过去就行了。但是实际上,这一步还必须完成创建目录、保存文件、设置权限等步骤。这整个的保存过程就称为"安装"(Installation)。
第九步 操作系统连接
可执行文件安装后,必须以某种方式通知操作系统,让其知道可以使用这个程序了。比如,我们安装了一个文本阅读程序,往往希望双击txt文件,该程序就会自动运行。
这就要求在操作系统中,登记这个程序的元数据:文件名、文件描述、关联后缀名等等。Linux系统中,这些信息通常保存在/usr/share/applications目录下的.desktop文件中。另外,在Windows操作系统中,还需要在Start启动菜单中,建立一个快捷方式。
这些事情就叫做"操作系统连接"。make install命令,就用来完成"安装"和"操作系统连接"这两步。
第十步 生成安装包
写到这里,源码编译的整个过程就基本完成了。但是只有很少一部分用户,愿意耐着性子,从头到尾做一遍这个过程。事实上,如果你只有源码可以交给用户,他们会认定你是一个不友好的家伙。大部分用户要的是一个二进制的可执行程序,立刻就能运行。这就要求开发者,将上一步生成的可执行文件,做成可以分发的安装包。
所以,编译器还必须有生成安装包的功能。通常是将可执行文件(连带相关的数据文件),以某种目录结构,保存成压缩文件包,交给用户。
第十一步 动态连接(Dynamic linking)
正常情况下,到这一步,程序已经可以运行了。至于运行期间(runtime)发生的事情,与编译器一概无关。但是,开发者可以在编译阶段选择可执行文件连接外部函数库的方式,到底是静态连接(编译时连接),还是动态连接(运行时连接)。所以,最后还要提一下,什么叫做动态连接。
前面已经说过,静态连接就是把外部函数库,拷贝到可执行文件中。这样做的好处是,适用范围比较广,不用担心用户机器缺少某个库文件;缺点是安装包会比较大,而且多个应用程序之间,无法共享库文件。动态连接的做法正好相反,外部函数库不进入安装包,只在运行时动态引用。好处是安装包会比较小,多个应用程序可以共享库文件;缺点是用户必须事先安装好库文件,而且版本和安装位置都必须符合要求,否则就不能正常运行。
现实中,大部分软件采用动态连接,共享库文件。这种动态共享的库文件,Linux平台是后缀名为.so的文件,Windows平台是.dll文件,Mac平台是.dylib文件。
(文章完)
=====================================================
以下为广告部分。欢迎大家在我的网络日志,推广自己的产品。今天介绍的是。
[赞助商广告]
优秀的人才找到合适的归宿,是这个世界最幸福的事情之一。通过创新的拍卖方式,致力于帮助优秀程序员寻找归宿,给予求职者更多更好的职业选择。
过去三个月,100offer中成功的求职者,平均薪资涨幅高于30%,在2周内拿到3-5个offer。100offer与传统招聘网站存在极大差异,主要为下:
1、只接受部分候选人:100offer目前仅仅接受年薪高于15万,有一二线知名互联网公司工作经验的优秀程序员申请者。
2、反向模式:传统招聘网站是写简历投递给多家公司,而这里程序员只需要提交一次简历给offer,待审核通过后,100offer会邀约平台企业来竞拍候选人,产生一次投递数百家互联网公司的效果。拍卖时程序员会接受到来自各公司新鲜热辣的面试邀请,体验与传统网站截然不同。
3、绝对隐私:担心自己的隐私被雇主看到是完全不必要的:1、候选人同意面试邀请前,公司是完全看不到候选人的姓名、联系方式等隐私信息。2、拍卖开始前,候选人可以手动屏蔽掉3家公司,他们将永远看不到你的简历!
已经有众多大牛程序员通过100offer找到心仪的工作,目前11月候选人在征集中,点击图片并提交完整简历的程序员朋友,即可获赠15元亚马逊礼品卡!(活动截止期为日)
100offer目前阶段对企业免费,欢迎极客型创业公司和有实力的互联网公司前来!
越来越多的软件,开始采用云服务。
Email 是最常用的用户识别手段。
TCP 是互联网核心协议之一,本文介绍它的基础知识。
本文介绍一种简单高效、非常安全的加密方法:XOR 加密。gcc编译基本用法 - CSDN博客
gcc编译基本用法
gcc的基本用法
命令格式:gcc [选项] [文件名]
编译的四个阶段:
-E:仅执行编译预处理;
-c:仅执行编译操作,不进行连接操作;
-S:将C代码转换为汇编代码;
-o:指定生成的输出文件。
–c是使用GNU汇编器将源文件转化为目标代码之后就结束,在这种情况下,只调用了C编译器(ccl)和汇编器(as),而连接器(ld)并没有被执行,所以输出的目标文件不会包含作为Linux程序在被装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序
-c表示只编译(compile),而不连接成为可执行文件。生成同名字的 .o 目标文件。通常用于编译不包含主程序的子程序文件。
gcc -c hello.c
生成:hello.o
-o选项用于说明输出(output)文件名,gcc将生成一个目标(object)文件xx。
gcc hello.c -o xqf
或者:gcc -o xqf hello.c(顺序可以调换)
输出:xqf 为程序可执行文件
-g 选项产生符号调试工具(GNU的gdb)所必要的符号信息,插入到生成的二进制代码中。表示编译DEBUG版本。
想要对源代码进行调试,就必须加入这个选项。当然,会增加可执行文件的大小。
gcc study.c -o xqf
gcc -g study.c -o xqf_g
结果如下:(确实加了 -g 可执行文件后变大了一点)
-rwxr-xr-x 1 root root 12393 Apr 19 21:39 xqf_g
-rwxr-xr-x 1 root root 11817 Apr 19 20:48 xqf
gcc 在产生调试符号时,同样采用了分级的思路,开发人员可以通过在 -g 选项后附加数字1、2、3指定在代码中加入调试信息的多少。默认的级别是2(-g2),此时产生的调试信息包括:扩展的符号表、行号、局部或外部变量信息。
级别3(-g3)包含级别2中的所有调试信息以及源代码中定义的宏。
级别1(-g1)不包含局部变量和与行号有关的调试信息,因此只能够用于回溯跟踪和堆栈转储。
回溯追踪:指的是监视程序在运行过程中函数调用历史。
堆栈转储:则是一种以原始的十六进制格式保存程序执行环境的方法。
-pedantic 选项:当gcc在编译不符合ANSI/ISO C 语言标准的源代码时,将产生相应的警告信息
#include &stdio.h&
int main()
long long int var = 1;
printf(&hello world!\n&);
gcc -pedantic -o mm study.c
study.c: In function ‘main’:
study.c:5: warning: ISO C90 does not support ‘long long’
-Wall选项:使gcc产生尽可能多的警告信息,警告信息很有可能是错误的来源,特别是隐式编程错误,所以尽量保持0 warning。
用上面的代码:study.c,编译如下
gcc -Wall -o he study.c
study.c: In function ‘main’:
study.c:5: warning: unused variable ‘var’
-Werror 选项:要求gcc将所有的警告当作错误进行处理。
同样是上面的程序:study.c
gcc -Werror -o haha study.c
竟然没有错误!!
改一下study.c
#include &stdio.h&
void main()
long long int var = 1;
printf(&hello world!\n&);
//return 0;
gcc -Werror -o haha study.c
cc1: warnings being treated as errors
study.c: In function ‘main’:
study.c:4: error: return type of ‘main’ is not ‘int’
gcc -Wall -o hehe study.c
study.c:3: warning: return type of ‘main’ is not ‘int’
study.c: In function ‘main’:
study.c:5: warning: unused variable ‘var’
所以说:并不是所有的warning都变成 error。具体的,后面再深究。
-fPIC选项。PIC指Position Independent Code。共享库要求有此选项,以便实现动态连接(dynamic linking)。
-I 选项(大写的 i):向头文件搜索目录中添加新的目录。
1、用#include&file&的时候,gcc/g++会先在当前目录查找你所制定的头文件,如
果没有找到,他回到缺省的头文件目录找。
如果使用-I制定了目录,他会先在你所制定的目录查找,然后再按常规的顺序去找.
2、用#include&file&,gcc/g++会到-I制定的目录查找,查找不到,然后将到系统的缺
省的头文件目录查找
gcc –I /usr/dev/mysql/include test.c –o test.o
-l选项(小写的 l)说明库文件的名字。如果库文件为 libtest.so, 则选项为: -ltest
-L选项说明库文件所在的路径。
例如:-L.(“.”表示当前路径)。
&&&&& -L/usr/lib (“/usr/lib” 为路径。注:这里的路径是绝对路径)
如果没有提供 -L选项,gcc 将在默认库文件路径下搜索
-shared选项指定生成动态连接库,不用该标志外部程序无法连接。相当于一个可执行文件, 生成 .so 文件
-static 选项,强制使用静态链接库,生成 .a 文件。因为gcc在链接时优先选择动态链接库,只有当动态链接库不存在时才使用静态链接库。加上该选项可强制使用静态链接库。
.so 和 .a 的区别:运行时动态加载,编译时静态加载
具体的例子在文章:linux so文件生成与链接中有讲。
多个文件一起编译:
文件:test_a.c& test_b.c
两种编译方法:
1、一起编译
gcc test_a.c test_b.c -o test
2、分别编译各个源文件,之后对编译后输出的目标文件链接
gcc -c test_a.c
gcc -c test_b.c
gcc -o test_a.o test_b.o -o test
比较:第一中方法编译时需要所有文件重新编译;第二种植重新编译修改的文件,未修改的不用重新编译。
本文已收录于以下专栏:
相关文章推荐
Gcc的编译流程分为了四个步骤:
1.预处理,生成预编译文件(.文件):
Gcc –E hello.c –o hello.i
2.编译,生成汇编代码(.s...
参看:GCC编译过程分解
一、GCC简介:
gcc的原名叫做GNU C语言 编译器(GNU C Compile),只能编译C语言程序,后来很快就做了扩展,支持了更多的编程语言,比如C+ Object...
-E:只进行预处理,不编译
-S:只编译,不汇编
-c:只编译、汇编,不链接
-g:包含调试信息
-I:指定include包含文件的搜索目录
gcc编译流程分为4个步骤,分别为:
Gcc 编译与安装
(robinjun)
在GCC网站上(http://gcc.gnu.org)或者通过网上搜索可以查找到下载资源。目前GCC的最新版本为 4.2.1。可供下载的...
GCC编译器非常强大 ,在各个发行的linux系统中都非常流行,本文介绍的是一些常用的gcc编译选项
下面这段代码将回围绕整个文章:
编辑main.c如下.
1. gcc -E source_file.c
-E,只执行到预编译。直接输出预编译结果。
2. gcc -S source_file.c
-S,只执行到源代码到汇编代码的转换,输出汇编代码。
[Mac 10.7.1
Intel-based
gcc 4.2.1]
Q: 如何让编译的文件可以被gdb调试?
A: 可以加入-g参数。如下代码,保存为hello...
本文转自网络。
要想读懂本文,你需要对C语言有基本的了解,本文将介绍如何使用gcc编译器。首先,我们介绍如何在命令行方式下使用编译器编译简单的C源代码。然后,我们简要介绍一下编译器究竟作...
GCC rules开始...预编译编译汇编连接另外两个重要选项调试小结站点链接
要想读懂本文,你需要对C语言有基本的了解,本文将介绍如何使用gcc编译器。 ...
他的最新文章
讲师:董岩
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)14被浏览10199分享邀请回答gcc -o hello hello.c
开全部优化:gcc -O3 -o hello hello.c
带debug信息,调试的时候才能知道对应代码位置。debug的时候最好别开优化(或者优化级别调得低一些),除非你特别需要那样做:gcc -g -o hello hello.c
你可以看O' Reilly出的C++ cookbook那本书,讲了一些工程上的事情,比如怎么用gcc、cl、icc生成可执行文件、静态库、动态库。。。4添加评论分享收藏感谢收起51 条评论分享收藏感谢收起写回答如何使用gcc编译器? - Linx时代 - ChinaUnix.net
如何使用gcc编译器?
作者:Zhang Wei 翻译
20:24:41 来自:linuxfocus
要想读懂本文,你需要对C语言有基本的了解,本文将介绍如何使用gcc编译器。
首先,我们介绍如何在命令行方式下使用编译器编译简单的C源代码。
然后,我们简要介绍一下编译器究竟作了那些工作,以及如何控制编译过程。
我们也简要介绍了调试器的使用方法。
你能想象使用封闭源代码的私有编译器编译自由软件吗?你怎么知道编译器在你的
可执行文件中加入了什么?可能会加入各种后门和木马。Ken Thompson是一个著名
的黑客,他编写了一个编译器,当编译器编译自己时,就在'login'程序中留下后门
和永久的木马。请到
这个杰作的描述。幸运的是,我们有了gcc。当你进行
make install
gcc在幕后做了很多繁重的工作。如何才能让gcc为我们工作呢?我们将开始编写一个纸牌游戏,
不过我们只是为了演示编译器的功能,所以尽可能地精简了代码。
我们将从头开始一步一步地做,以便理解编译过程,了解为了制作可执行文件需要
做些什么,按什么顺序做。我们将看看如何编译C程序,以及如何使用编译选项
让gcc按照我们的要求工作。步骤(以及所用工具)如下:
(gcc -E),
首先,我们应该知道如何调用编译器。实际上,这很简单。我们将从那个著名的第一个C程序开始。
(各位老前辈,请原谅我)。
#include &stdio.h&
int main()
printf("Hello World!\n");
把这个文件保存为 game.c。 你可以在命令行下编译它:
gcc game.c
在默认情况下,C编译器将生成一个名为 a.out 的可执行文件。
你可以键入如下命令运行它:
Hello World
每一次编译程序时,新的 a.out 将覆盖原来的程序。你无法知道是哪个
程序创建了 a.out。我们可以通过使用 -o 编译选项,告诉
gcc我们想把可执行文件叫什么名字。我们将把这个程序叫做 game,我们
可以使用任何名字,因为C没有Java那样的命名限制。
gcc -o game game.c
Hello World
到现在为止,我们离一个有用的程序还差得很远。如果你觉得沮丧,你可以想一想我们
已经编译并运行了一个程序。因为我们将一点一点为这个程序添加功能,所以我们必须
保证让它能够运行。似乎每个刚开始学编程的程序员都想一下子编一个1000行的程序,
然后一次修改所有的错误。没有人,我是说没有人,能做到这个。你应该先编一个可以
运行的小程序,修改它,然后再次让它运行。这可以限制你一次修改的错误数量。另外,
你知道刚才做了哪些修改使程序无法运行,因此你知道应该把注意力放在哪里。这可以
防止这样的情况出现:你认为你编写的东西应该能够工作,它也能通过编译,但它就是
不能运行。请切记,能够通过编译的程序并不意味着它是正确的。
下一步为我们的游戏编写一个头文件。头文件把数据类型和函数声明集中到了一处。
这可以保证数据结构定义的一致性,以便程序的每一部分都能以同样的方式看待一切事情。
#ifndef DECK_H
#define DECK_H
#define DECKSIZE 52
typedef struct deck_t
int card[DECKSIZE];
/* number of cards used */
#endif /* DECK_H */
把这个文件保存为 deck.h。只能编译 .c 文件,
所以我们必须修改 game.c。在game.c的第2行,写上 #include "deck.h"。
在第5行写上 deck_。为了保证我们没有搞错,把它重新编译一次。
gcc -o game game.c
如果没有错误,就没有问题。如果编译不能通过,那么就修改它直到能通过为止。
编译器是怎么知道 deck_t 类型是什么的呢?因为在预编译期间,
它实际上把"deck.h"文件复制到了"game.c"文件中。源代码中的预编译指示以"#"为前缀。
你可以通过在gcc后加上 -E 选项来调用预编译器。
gcc -E -o game_precompile.txt game.c
wc -l game_precompile.txt
3199 game_precompile.txt
几乎有3200行的输出!其中大多数来自 stdio.h 包含文件,但是如果
你查看这个文件的话,我们的声明也在那里。如果你不用 -o 选项指定
输出文件名的话,它就输出到控制台。预编译过程通过完成三个主要任务给了代码很大的
把"include"的文件拷贝到要编译的源文件中。
用实际值替代"define"的文本。
在调用宏的地方进行宏替换。
这就使你能够在整个源文件中使用符号常量(即用DECKSIZE表示一付牌中的纸牌数量),
而符号常量是在一个地方定义的,如果它的值发生了变化,所有使用符号常量的地方
都能自动更新。在实践中,你几乎不需要单独使用 -E 选项,而是让它
把输出传送给编译器。
作为一个中间步骤,gcc把你的代码翻译成汇编语言。它一定要这样做,它必须通过分析
你的代码搞清楚你究竟想要做什么。如果你犯了语法错误,它就会告诉你,这样编译就失败了。
人们有时会把这一步误解为整个过程。但是,实际上还有许多工作要gcc去做呢。
as 把汇编语言代码转换为目标代码。事实上目标代码并不能在CPU上运行,
但它离完成已经很近了。编译器选项 -c 把 .c 文件转换为以 .o 为扩展名
的目标文件。
如果我们运行
gcc -c game.c
我们就自动创建了一个名为game.o的文件。这里我们碰到了一个重要的问题。我们可以用
任意一个 .c 文件创建一个目标文件。正如我们在下面所看到的,在连接步骤中我们可以
把这些目标文件组合成可执行文件。让我们继续介绍我们的例子。因为我们正在编写一个
纸牌游戏,我们已经把一付牌定义为 deck_t,我们将编写一个洗牌函数。
这个函数接受一个指向deck类型的指针,并把一付随机的牌装入deck类型。它使用'drawn'
数组跟踪记录那些牌已经用过了。这个具有DECKSIZE个元素的数组可以防止我们重复使用
#include &stdlib.h&
#include &stdio.h&
#include &time.h&
#include "deck.h"
static time_t seed = 0;
void shuffle(deck_t *pdeck)
/* Keeps track of what numbers have been used */
int drawn[DECKSIZE] = {0};
/* One time initialization of rand */
if(0 == seed)
seed = time(NULL);
srand(seed);
for(i = 0; i card[i] =
pdeck->dealt = 0;
把这个文件保存为 shuffle.c。我们在这个代码中加入了一条调试语句,
以便运行时,能输出所产生的牌号。这并没有为我们的程序添加功能,但是现在到了
关键时刻,我们看看究竟发生了什么。因为我们的游戏还在初级阶段,我们没有别的
办法确定我们的函数是否实现了我们要求的功能。使用那条printf语句,我们就能准确
地知道现在究竟发生了什么,以便在开始下一阶段之前我们知道牌已经洗好了。在我们
对它的工作感到满意之后,我们可以把那一行语句从代码中删掉。这种调试程序的技术
看起来很粗糙,但它使用最少的语句完成了调试任务。以后我们再介绍更复杂的调试器。
请注意两个问题。
我们用传址方式传递参数,你可以从'&'(取地址)操作符看出来。这把变量的机器地址
传递给了函数,因此函数自己就能改变变量的值。也可以使用全局变量编写程序,但是应该
尽量少使用全局变量。指针是C的一个重要组成部分,你应该充分地理解它。
我们在一个新的 .c 文件中使用函数调用。操作系统总是寻找名为'main'的函数,并从
那里开始执行。 shuffle.c 中没有'main'函数,因此不能编译为独立的可执行文件。
我们必须把它与另一个具有'main'函数并调用'shuffle'的程序组合起来。
gcc -c shuffle.c
并确定它创建了一个名为 shuffle.o 的新文件。编辑game.c文件,在第7行,在
deck_t类型的变量 deck 声明之后,加上下面这一行:
shuffle(&deck);
现在,如果我们还象以前一样创建可执行文件,我们就会得到一个错误
gcc -o game game.c
/tmp/ccmiHnJX.o: In function `main':
/tmp/ccmiHnJX.o(.text+0xf): undefined reference to `shuffle'
collect2: ld returned 1 exit status
编译成功了,因为我们的语法是正确的。但是连接步骤却失败了,因为
我们没有告诉编译器'shuffle'函数在哪里。
那么,到底什么是连接?我们怎样告诉编译器到哪里寻找这个函数呢?
连接器ld,使用下面的命令,接受前面由 as
创建的目标文件并把它转换为可执行文件
gcc -o game game.o shuffle.o
这将把两个目标文件组合起来并创建可执行文件 game。
连接器从shuffle.o目标文件中找到 shuffle 函数,并把它包括进可执行文件。
目标文件的真正好处在于,如果我们想再次使用那个函数,我们所要做的就是包含"deck.h"
文件并把 shuffle.o 目标文件连接到新的可执行文件中。
象这样的代码重用是经常发生的。虽然我们并没有编写前面作为调试语句调用的 printf
函数,连接器却能从我们用 #include &stdlib.h& 语句包含的文件中
找到它的声明,并把存储在C库(/lib/libc.so.6)中的目标代码连接进来。
这种方式使我们可以使用已能正确工作的其他人的函数,只关心我们所要解决的问题。
这就是为什么头文件中一般只含有数据和函数声明,而没有函数体。一般,你可以为
连接器创建目标文件或函数库,以便连接进可执行文件。我们的代码可能产生问题,因为
在头文件中我们没有放入任何函数声明。为了确保一切顺利,我们还能做什么呢?
-Wall 选项可以打开所有类型的语法警告,以便帮助我们确定代码是正确的,
并且尽可能实现可移植性。当我们使用这个选项编译我们的代码时,我们将看到下述警告:
game.c:9: warning: implicit declaration of function `shuffle'
这让我们知道还有一些工作要做。我们需要在头文件中加入一行代码,以便告诉编译器有关
shuffle 函数的一切,让它可以做必要的检查。听起来象是一种狡辩,但这样做
可以把函数的定义与实现分离开来,使我们能在任何地方使用我们的函数,只要包含新的头文件
并把它连接到我们的目标文件中就可以了。下面我们就把这一行加入deck.h中。
void shuffle(deck_t *pdeck);
这就可以消除那个警告信息了。
另一个常用编译器选项是优化选项 -O# (即 -O2)。
这是告诉编译器你需要什么级别的优化。编译器具有一整套技巧可以使你的代码运行得更快一点。
对于象我们这种小程序,你可能注意不到差别,但对于大型程序来说,它可以大幅度提高运行速度。
你会经常碰到它,所以你应该知道它的意思。
我们都知道,代码通过了编译并不意味着它按我们得要求工作了。你可以使用下面的命令验证
是否所有的号码都被使用了
game | sort - n | less
并且检查有没有遗漏。如果有问题我们该怎么办?我们如何才能深入底层查找错误呢?
你可以使用调试器检查你的代码。大多数发行版都提供著名的调试器:gdb。如果那些众多的命令行选项
让你感到无所适从,那么你可以使用KDE提供的一个很好的前端工具 。
还有一些其它的前端工具,它们都很相似。要开始调试,你可以选择 File-&Executable 然后找到你的 game 程序。
当你按下F5键或选择 Execution-&gt从菜单运行时,你可以在另一个窗口中看到输出。
怎么回事?在那个窗口中我们什么也看不到。不要担心,KDbg没有出问题。问题在于我们
在可执行文件中没有加入任何调试信息,所以KDbg不能告诉我们内部发生了什么。编译器选项
-g 可以把必要的调试信息加入目标文件。你必须用这个选项编译目标文件
(扩展名为.o),所以命令行成了:
gcc -g -c shuffle.c game.c
gcc -g -o game game.o shuffle.o
这就把钩子放入了可执行文件,使gdb和KDbg能指出运行情况。调试是一种很重要的技术,很
值得你花时间学习如何使用。调试器帮助程序员的方法是它能在源代码中设置“断点”。现在你可以
用右键单击调用 shuffle 函数的那行代码,试着设置断点。那一行边上会出现一个
红色的小圆圈。现在当你按下F5键时,程序就会在那一行停止执行。按F8可以跳入shuffle函数。
呵,我们现在可以看到 shuffle.c 中的代码了!我们可以控制程序一步一步地执行,
并看到究竟发生了什么事。如果你把光标暂停在局部变量上,你将能看到变量的内容。
太好了。这比那条 printf 语句好多了,是不是?
本文大体介绍了编译和调试C程序的方法。我们讨论了编译器走过的步骤,以及为了让
编译器做这些工作应该给gcc传递哪些选项。我们简述了有关连接共享函数库的问题,
最后介绍了调试器。真正了解你所从事的工作还需要付出许多努力,但我希望本文
能让你正确地起步。你可以在 gcc、 as 和 ld的 man 和 info page中
找到更多的信息。
自己编写代码可以让你学到更多的东西。作为练习你可以以本文的纸牌游戏为基础,编写
一个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 GNU Compiler Collection
GNU Debugger
KDE's GUI Debugger
Ken Thompson's great compiler hack
(编辑:admin)
【】【评论】【】
Linux文档搜索

我要回帖

更多关于 gcc编译器 的文章

 

随机推荐