- UID
- 850901
|
运行命令
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 语句包含的文件中 找到它的声明,并把存储在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提供的一个很好的前端工具 KDbg。 还有一些其它的前端工具,它们都很相似。要开始调试,你可以选择 File->Executable 然后找到你的 game 程序。 当你按下F5键或选择 Execution->从菜单运行时,你可以在另一个窗口中看到输出。 怎么回事?在那个窗口中我们什么也看不到。不要担心,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传递哪些选项。我们简述了有关连接共享函数库的问题, 最后介绍了调试器。真正了解你所从事的工作还需要付出许多努力,但我希望本文 能让你正确地起步 |
|