在UNIX系统中,实现C源程序到可执行文件的这一转换过程的工具是cc。在大多数系统中cc实际上是一个shell命令文件。有些系统中的C编译程序可能并不叫cc而是其它的一个什么名称,如Sun工作站上常用的gcc等等。但这些都无关紧要。大多数系统中C编译命令的用法基本上都是类似的。我们这里介绍的将以SVR4上的C编译系统为基础。
cc基本用法
一般我们只需要将C源程序的名字写在CC命令行中,cc即可对这些源文件(.c文件)进行编译。如果这些源文件中都没有main()函数的定义,那么cc将只能生成与各源文件相对应的目标文件(.o文件)。如果某个源文件中有关于main()函数的定义,则将把所有目标文件链接起来生成相应的可执行文件。缺省的情况下这个可执行文件的名字将是a.out。
例如,假定myprog.c是一个包含有main()函数定义的C语言程序文件,其中代码如下:
/*********************************************
* An example source code with errors *
* Name:myprog.c *
********************************************* /
#include <stdio.h>
#include <ctype.h>
# define TESTOK 1
int TestInput(char * ValuInput)
{while (* ValueInput)
if (! isdigit(* ValueInput )) return (! TESTOK);
else ValueInput + +;
return ((100/atoi(ValueInput))? TESTOK:! TESTOK);
}
void
main(int argc,char * argv[])
{int i;
for (i=1;i<argc;;i++)
if(TestInput (argv[i]) = =TESTOK)
printf("The %dth value '%s' \tis OK! \n",i,argv[i]);
else
printf("The %dth value '%s' \tis BAD! \n" ,i,argv[i]);
}
对于此程序中的错误(设计错误)我们暂不理会。下一章我们介绍程序调试时再回过头来看看如何排除这个错误。
我们看到。在这个源程序文件中,定义了两个函数:TestInput()和main(),定义了一个宏TESTOK,同时包含了两个标准的头文件。为了把这个C程序转换成可执行文件,在shell提示符下输入:
$cc myprog.c
在程序中没有任何语法错误的情况下,cc将在当前目录下生成一个名为a.out的可执行文件,如:
$ cc myproc.c
$ ls -l
-rwx------ 1 yxz users 5812 Aug 31 15:32 a.out
-rw------- 1 yxz users 716 Aug 31 15:27 myproc.c
$
还可以看到这里a.out是一个可执行文件。当然这个程序由于在设计上有些失误,我们现在还不能马上就带参数运行。但不带参数运行还是可以的。只不过此时该程序什么都没有干,如:
$ a.out
$
在程序中我们通过main函数的两个参数argc和argv而使程序能够引用shell命令行参数;这是UNIX环境下一种常用的编程技术。
在生成了a.out文件之后,我们自然可用mv命令将其修改为某个合适的名称。但更简单的方法是在cc命令行中加上-o选项,使cc直接将可执行文件写入到指定的文件中而不生成a.out文件,如:
$ cc -o myprog myprog.c
$ ls -l myprog
total 14
-rwx------ 1 yxz users 5812 Aug 31 15:34 myprog
-rw------- 1 yxz users 716 Aug 31 15:27 myprog.c
$
我们看到,myprog这个文件除了文件名及修改时间同a.out不一样外,其他属性同a.out都是一摸一样的。这也说明了两者的等价性。
在某个程序的源代码被存放到多个不同文件中的情况下,我们只需要在命令行中一一指定这多个C文件即可。例如,我们可以将上述myprog.c拆分为两个C文件和一个头文件(.h)如下:
myprog.h
# include <stdio.h>
# inclued <ctype.h>
# define TESTOK 1
myprog.c
#include "myprog.h"
void
main (int argc,char * argv[])
{int i;
for (i=1;i<argc;i + +)
if (TestInput(argv[i])= = TESTOL)
printf("The %dth value '%s' \tis ok! \n",argv[1]);
else
printf("The %dth value ' %s' \tis BAD! \n",iargv[i]);
}
myfunc.c
#include "myprog.h"
int
TestInput(char * ValueInput)
{while (* ValueInput)
if (!isdigit(*ValueInput) return (! TESTOK);
else ValueInput + +
return ((100/atoi(ValueInput))? TESTOK:! TESTOK);
这时要再编译此程序时可输入如下命令:
$ cc -o myprog myprog.c myfunc.c
在这个命令行中如果不指定myfunc.c,此时由于在myprog.c中所调用的TestInput()这个函数不是任何标准的库函数,在链接时链接程序将找不到此符号的定义,故链接过程将以失败而告终,此时cc将给出如下的错误信息:
Undefine first referenced
symbol in file
TestInput myprog.o
id: myprog:fatal error: Symbol referencing errors.No output written to myprog
$
而可执行文件myprog也无法生成。但编译却会生成myprog.c的目标代码(在某个文件固有语法错误而无法正确被编译的情况下(此时为编译过程出错),cc将生成其他无语法错误的源文件的目标文件,但不进行链接)。如下:
$ ls -l
total 8
rw-r--r-- 1 yxz user 454 Sep 1 09:27 myfunc.c
rw-r--r-- 1 yxz user 479 Sep 1 09:28 myprog.c
rw-r--r-- 1 yxz user 298 Sep 1 09:27 myprog.h
rw-r--r-- 1 yxz user 924 Sep 1 09:28 myfunc.o
此时我们可以使用如下命令行得到可执行文件:
$ cc -o myprog myprog.o myfunc.c
这里我们看到,cc命令行中的文件参数可以不全是.c文件,目标文件(.o)文件以后编译过程中所得到的其他文件,如预编译后文件(.i文件),编译后的汇编程序(.s文件)等都可作为文件参数。在了解了UNIX C编译系统的工作过程之后,理解这一点是不困难的。因为编译系统只需要对各种不同类型的文件进行有关的处理就可以了。
关于cc命令最基本的用法我们就介绍这么多,其它更高级的用法可参考下面几节的讨论。
常用选项
cc命令还提供了其他许多有用的命令行选项。借助于这些选项我们可以对编译过程进行进一步的控制,如使cc只完成某些阶段的编译工作,指定对头文件的搜索目录,指定对代码进行优化,指定在代码中加入一些供调试程序所用的信息,等等。下面我们分别讨论这些问题。
1.仅进行编译预处理
在命令行中加上-P选项可以使cc仅完成对.c文件的预处理工作,而后面的编译,汇编,优化,链接则都不作,例如:
$ cc -P myprog.c
此时编译系统将在当前目录下生成一个名为myprog.i的文件。这个文件中包含有对myprog.c中的伪指令进行处理后的代码及myprog.c中原有的代码。
在某些情况下,.i文件可能对于程序排错有一定的用处。对于下面的代码段,编译程序可能会报告j无定义的错误:
for (i=0;i<10;i++)
{ /*declare avariable j:
int j;/*This is a temporary variable */
j=i*i;
.
.
.
}
预编译处理后,缺省情况下,预处理程序将把源程序中的注释删除,这样改段代码将变成:
for (i=0;i<10;i++)
{
j=i*i;
.
.
.
}
这样一来我们将能够比较快地发现程序中的错误。利用.i文件,我们还能够对条件编译和宏扩展后的结果进行检查。
在cc命令行中加上-C选项可以在预编译后的文件中保留源文件中的注释。
2.仅生成汇编语言代码
在cc命令行中加上-S 选项,可以使cc只调用预处理程序和编译程序以生成与源程序相应的汇编代码。与每一个C源文件相应的汇编程序被放到相应的.s文件中。例如:
$ cc -S myprog.c myfunc.c
$ ls *.s
myfunc.s myprog.s
$
这种汇编语言代码是同机器具体相关的。有些情况下我们可能需要用汇编语言进行编程,这时可以先用C语言编写此程序,再编译得到汇编程序,然后手工对此汇编程序修修改改,估计基本上就能满足要求。由于用汇编语言进行编程是一件效率比较低的工作,用此种方法可以预期将获得比较高的效率。
3.仅生成目标文件
如果只想生成源文件的目标代码而不对这些代码进行链接,可以在cc命令行中加上-C选项。此时编译系将只生成与各源文件相对应的.o文件(目标文件)。如:
$ cc -c myprog.c myfunc.s
将生成同myprog.c和myfunc.s相对应的目标文件myprog.o和myfunc.o。
4.头文件搜索路径
当用户在C源程序中用# include指令包含了某个头文件时,根据文件名指定方法的不同,C编译系统将在不同的目录下去寻找指定的头文件:
在用尖括号(<>)指定头文件名时,预处理程序将在系统中存放头文件的标准位置(通常是/usr/include目录)寻找指定的头文件。
在用双引号(“”)指定投文件名时,预处理程序将先在包含此头文件的C源程序所在的目录中(一般为当前目录),去查找该头文件。找不到时再到标准目录下去查找。
在对于那些头文件既不在标准位置,又不在与C源程序同一目录时的情况怎么办呢?为此,CC命令提供了-I(Include)选项,以供用户自己指定头文件所在地目录。例如,对于myfunc.c和myprog.c中所包含的头文件myprog.h,我们假定其后来被放在目录$HOME/include目录下,而这两个C文件则被放在$HOME/cfile目录下。此时在$HOME/cfile目录下对这两个C文件进行编译时,可使用如下命令:
$ cc -I #HOME/include myprog.c myfunc.c
此时对于这两个C文件中的#include "myprog.h",预处理程序将先在$HOME/cfile目录下,然后再$HOME/include目录下,最后再系统标准位置查找myprog.h。-I选项也能改变那些用尖括号(<>)指定的头文件的搜索顺序,此时预编译程序将首先在-I指定的目录下,然后才在标准位置搜索。
-I选项可以多次重复使用。这样我们将能够指定多个非标准的头文件目录。
5.在目标文件中加入调试用的信息
除非是那种特别简单的程序,一般大多数程序都会有这样或那样的问题。为了能够使用UNIX的符号调试程序(sdb,下一章回具体介绍)对程序进行调试,必须在目标代码中加入一些有关的程序变量和语句信息,以便sdb能够跟踪函数调用、显示变量的值以及设置断点,等等。
在cc命令行中加入-g选项将能够实现上述要求,如:
$ cc -g -o myprog myprog.c myfunc.c
这样生成的myprog就可以用sdb进行调试了。
6.优化处理
优化的含义前面我们已经讲过,这里不想再重复。我们要说明的是在程序的调试过程中用不着进行优化处理。优化只应对最终提交的可执行程序进行。
在CC命令行中加上-O选项可以使编译系统对代码进行优化:
$ cc -O -o myprog myprog.c myfunc.c
优化对于不同的程序效果可能是不同的。有些程序优化不优化都不会有什么区别。在有些系统上(如Sun OS),对程序的优化可以分成不同的级别(一般是1至4级)。第一级优化是仅在汇编级上优化,这是大多数系统都会做得。第二级优化是全局优化,如循环优化、公共子表达式的消除、复写传播及自动寄存器的分配。第三级上的优化再加上对外部变量的用法和定义的优化。第四级优化则在第三级基础上对指针赋值得效果进行跟踪。程序员可在-O后面加上一个数字(1,2,3,4)来表示所希望的优化级别。
在cc命令行中还可以使用其他的许多选项,下一节我们将介绍同链接有关的一些选项,其他选项的使用请参阅联机帮助。或者使用手册。