一、基础研究
在这里我们需要提供一套新的c语言开发工具cc,它支持的c程序不是从main开始运行而是从CMain开始运行。
书上已经对该工具程序进行了需求分析:(1)要在屏幕中间显示彩色的字符串;(2)等待用户输入,按下任意键后开始运行程序员写的程序。
也给出了由需求分析进行的功能分析:代码文件main.obj实现打印字符串、等待输入、调用程序的功能。编译链接文件cc.exe实现调用tcc编译文件、调用tlink连接文件的功能。
新建文件夹,在其中实现main.c如图:
将main.c编译成main.obj文件,并将tcc.exe、tlink.exe以及编译连接所需文件都拷贝到文件夹中。
这里我们要实现一个编译连接程序cc.c与c.bat功能相同。
那么首先来看看什么是批处理文件:批处理就是对某对象进行批量的处理。批处理文件的扩展名为.bat。在“命令提示”下键入批处理文件的名称,或者双击该批处理文件,系统就会调用Cmd.exe运行该批处理程序。我们来看看c.bat的内容:
这里@表示不显示@后面的命令,echo是一条批处理指令,echo off是关闭回显功能,即后面的语句执行时都不会在屏幕上显示。%1表示传过来的参数。
但是这里批处理使用了dos命令tcc和tlink,而dos命令是不能直接写在c程序里的,能否在c程序里使用dos命令呢?我们来看system函数的功能,它可以发出一个dos命令,如我们要在c程序里使用命令tcc main,可以用system(“tcc main”);来实现。这里还要用到的一个函数strcat,可以把一个字符串添加到另一个字符串结尾处,覆盖另一个字符串结尾处的'\0')并添加'\0',合成一个完整的字符串。
一个参考程序putarg.c,我们来看看它的内容:
这个程序的作用是将arg字符数组的值作为一个字符串输出,而且我们要注意这里的参数arg是一个二阶指针,它的作用是什么呢?我们来运行一下:
首先输出了当前的绝对路径,然后输出了我们加在后面的参数,这说明程序是将命令行的参数名作为字符串即字符数组的形式来处理的,*arg的值为参数的字符串的首地址,而**arg的值是字符串的每一个字符。为什么这里n代表命令行参数的个数,arg指向参数的首地址呢?查找资料可知这是main函数不是作为普通函数来使用的,所以它的参数是有特殊用途的。main函数如果带参有两个参数,那么第一个表示参数的个数;第二个参数中argv[0]为自身运行目录路径和程序名,argv[1]指向第一个参数、argv[2]指向第二个参数、等等。
那么我们就可以通过使用带参数的main函数接收我们要编译连接的c文件名,然后将它用strcat进行处理,在用system执行进行编译连接。
编写程序如下:
这里用数组a、b、c、d存储批处理文件里不用改的部分字符串。在开始进行判断,如果n小于2,说明在命令行没有输入要编译的文件名,则提示错误信息并返回。
之后将要编译的文件名加在数组a的后面,形成一句完整的tcc编译语句,将目标文件编译成obj文件。因为arg指针指向的第一个值是本程序的绝对路径,即arg[0]的值是本程序的绝对路径,而arg[1]是存储要编译的文件的字符串的首地址。然后用system执行数组a存放的tcc编译语句。
之后要判断字符串是否结束,如果未结束,则判断字符是否为’.’,因为我们如果在命令行输入的参数是XXX.c,那么tcc时后面加XXX.c是正确的,但是在tlink语句里面加XXX.c是错误的,应该用XXX或者XXX.obj,为了简便我们就使用名字。然后将“.”改成转义字符“\0”表示字符串已经结束。
之后再用strcat将tlink连接语句拼接好用system执行。
执行结果如下:
如果不输入文件名则提示错误。如果输入则正确编译连接。运行编译连接生成的exe文件如图:
先在屏幕中间显示显示彩色字符串,再执行CMain函数“welcome to c”,输出字符串“hello world!”。
二、扩展研究
(1)为什么main函数的参数是参数的个数和命令行参数字符串?
答:我认为是参数是在程序跳转到main函数之前初始化时将命令行的参数的字符串地址传递到栈里,再在main函数里通过栈调用。
三、研究总结
今天我们自己基于tcc、tlink实现了一个编译连接工具cc,它的功能与tcc相似,都是编译连接程序,只是多出了显示欢迎字符串和等待输入再执行程序的功能。向下看,其本质还是tcc的功能,我们并没有真正地实现一个编译器和连接器,向上看,这个程序的实现是一个不断集成的过程,再加上别的功能的obj文件可以组合成更大更丰富的程序。
这一章对共性和个性的分离和封装更加清晰了:共性被封装在编译工具里,个性由要编译的程序实现,按照这个思路,我们可以开发出功能更强大的编译连接工具。
这一张我觉得学习了一个很重要的函数system,我们可以通过程序来调用操作系统的功能,它与我们传统的思维是不同的,我觉得操作系统打开软件执行,软件实现功能后返回操作系统,而这里软件可以调用操作系统的功能,这使我们写的程序可以实现更强大的功能。这种操作是由顶层向底层的调用。