第二课¶
也许现在我们写下的程序并没有实际的用处,当时随着我们学习的深入,你会发现你可以让自己的程序做很多的事情。那么在这一节里,我们主要来介绍两个方面的内容:注释和编译器在创建你的程序过程中的各个不同的阶段。在这里,我们还要探讨一下如何调试你的代码。
注释¶
注释其实就是你在代码中添加的所要注意的地方。注释可以有很多用处,例如:可以对某一段代码的意图进行解释,提供警告,临时禁用某一段代码。至于如何来使用注释,请参看下面的例子:
// 这是一个单行的注释.
// 同上. 在两个正斜杠之后所有内容都是注释的一部分.
int main(void)
{
PushTheRedButton(); //在这一行中,两个斜杠之后的代码对程序运行没有任何影响.
return 1;
}
注释也可是使用从 C 语言继承过来的多行注释,它们以 “/” 和 “/” 作为开始和结束的标志,和圆括号和大括号的用法相似,唯一的不同点是:不能够把两个多行注释叠加在一起使用。
/*-------------------------------------------------------------------
RedButton.cpp
如果在我们的代码中包含了编译器无法识别的函数,将会发生什么呢,那么下面这个代码
就是一个例子.
---------------------------------------------------------------------*/
// 这是一个单行的注释.
// 同上. 在两个正斜杠之后的所有内容都是注释的一部分.
int main(void)
{
PushTheRedButton(); // 在这一行中,两个斜杠之后的代码对程序运行没有任何影响.
return 1;
}
那么接下来我们返回上一节的内容,看一下在编译器编译程序的过程中都经历了那些步骤。了解这些对于我们的变成是极为有利的,因为在编写代码的过程中将会出现各种各样的错误,而了解这个过程将可以让我们快速的找到问题的症结所在。
构建过程¶
在一个程序从源码开始编译的过程中,会有四个程序分别对它进行处理,它们分别是:预处理器,编译器,汇编器,和链接器。
第一步:预处理器¶
预处理器从开始接受源码到传递源码给编译器的过程中,只对源码做极少的处理。首先它将源码中的注释移去,然后将在 #include 后尖括号中所包含的头文件中的代码嵌入到源码中。还会有其他的一些指令的处理,这些指令的处理,我们将在以后在作讨论。
第二步:编译器¶
编译器将源码翻译为汇编语言。汇编语言还是可以被我们人类所理解的,但是它和机器语言已经是极为接近了。当然,使用汇编语言来编写程序是机器困难的,而且它会因为处理器的不同而不同,也就是与机器相关的。
调试程序¶
由于人类的天性,每个人都会犯错,程序员也是这样。编写代码,然后调试代码总是一步一步来的,有时候也可以同时进行。那么,接下来就让我们开始调试代码的学习。
Bug 通常有两种类型:语法错误和语义错误。在语法上的错误时很容易被找到的,而且编译器通常会为我们完成这项工作。语法上的错误有大小写错误,括号的丢失或者多余以及函数名的输入错误等等。但是语义上的错误的发现是非常不容易的,因为这些错误存在于完全合法的代码的逻辑之中。在英文中 The oxygen censor on my car needed to be replaced 是完全符合语法规定和正确组织的,但是这句话是存在语义上的错误的,因为我们应该用 “sensor” 来代替 “censor”。语义上的错误有下面的几种情形:一些地方多了一个分号,数字的赋值错误,函数的返回值产生歧义等。
下面是一些常见错误的例子:
例 1¶
源码:
#include <stdio.h>
int main(void)
{
return 1;
} }
错误:
foo.cpp:6: error: expected declaration before ‘}’ token
在这段代码中,产生了一个多出来的大括号。GCC 给出了一个正确的语法错误,而且还有两个提示:文件名和代码行号。由 gcc 给出行号和我们产生错误的行的行号并不总是一致的,但在这个例子中它们是相同的。
也许在这里,你会产生疑问,What in the world is a token, genius?,token 是一个语言元素。就像我们的语言中有单词和标点符号一样,计算机也有自己的词汇和标点。如果我们在一句话中接连使用两个逗号,这产生了一个标点使用错误,那么如果我们在 c++ 中多出来一个大括号,这也是 c++ 中的标点使用错误。
例 2¶
源码:
/*-----------------------------------------------------------------------
RedButton.cpp
/* 如果在我们的代码中包含了编译器无法识别的函数,将会发生什么呢,那么下面这个代码就是一个例子.*/
------------------------------------------------------------------------*/
// 这是一个单行的注释.
// 同上. 在两个正斜杠之后的所有内容都是注释的一部分.
int main(void)
{
PushTheRedButton(); // 在这一行中,两个斜杠之后的代码对程序运行没有任何影响.
return 1;
}
错误:
foo.cpp:7: error: expected unqualified-id before ‘--’ token
在上面这个例子中,错误提示给出的代码行号和实际错误的代码行号是不一致的。这个错误是由在顶部多行注释的结尾处出现的破折号所引起的。然而真正造成这个结果的是,在多行注释的内部添加了另一个多行注释。当预处理器将所有的注释移除之后,编译器接收到的代码是下面这个样子的:
----------------------------------*/
int main(void)
{
PushTheRedButton();
return 1;
}
那么接下来编译器不知道如何处理这些存在破折号的代码行,所以就报错了。
例3¶
源码:
int main(void)
{
return 1;
}
错误:
/usr/lib/gcc/i486-linux-gnu/4.4.1/../../../../lib/crt1.o: In function `_start':
/build/buildd/eglibc-2.10.1/csu/../sysdeps/i386/elf/start.S:115: undefined
reference to `main'
/tmp/ccv39Cuo.o:(.eh_frame+0x12): undefined reference to `__gxx_personality_v0'
collect2: ld returned 1 exit status
这是一种不同类型的错误。是否还记得在每个程序中 main() 函数是必不可少的?但是这里我们没有使用—使用的是 main()。这个程序原本是有效地,所以它可以顺利的完成编译,但是当链接器准备将目标代码组织在一起的时候,它不能够找到一个必须存在的函数,所以就发飙了,进行了罢工。所以不管什么时候当你看到一个错误包含 undefined reference 时,这就意味着产生了一个链接错误。
解决由链接器产生的 undefined reference 错误并不是很困难。通常产生这一错误意味着有两个问题:你没有将所需要的库函数链接到主函数中,或者当你在创建程序的时候,一个源码文件被意外的删掉了。