Tiny C Compiler最小的C语言编译器

欧阳嘉年
2023-12-01

TCC研究(1): Tiny C Compiler最小的C语言编译器,自己编译自己,tcccompiler


最近,在学习编译器技术, 研究了一下TCC, 记录一下。

Tiny C Compiler(简称TCC, 或Tiny CC)是世界上最小的C语言编译器,而且是开源的, 小到只有约100K. (VC, GCC动不动几十M几百M的, 汗)

别看它小, 功能还是很强. 可以编译所有C99标准的ANSI C程序 ,  支持CPU包括:X86 32或64位, ARM,支持Windows, Linux, OSX.(跨平台跨系统的能力,比VC强)

TCC是由大牛Fabrice Bellard开发的,这位大牛还开发过 Qemu, FFMpeg (没有FFMpeg, 就没有抄它的腾讯视频,暴风影音...)




一、下载TCC

主页: http://bellard.org/tcc/


下载页: http://download.savannah.gnu.org/releases/tinycc/




TCC最新版本是0.9.26

下载执行程序: tcc-0.9.26-win32-bin.zip  




无需安装的,只需要解压即可。


解压缩到  c:\tcc ,  可见目录下有 tcc.exe, 这个是编译器命令行程序,没有IDE界面的

将 c:\tcc 加入到 系统路径中 (PATH)后,则可以在任何命令行窗口中使用了。




二、使用TCC

用notepad写一个测试用的c程序,如下,存盘为 hello.c

#include <stdio.h>

main() {
    printf("hello,world\n");
}

用tcc编译,命令如下:

   tcc hello.c

则将生成  hello.exe ,    运行hello.exe看看




TCC可以不编译C程序,而是把它直接运行,就像脚本解释语言那样。命令行如下:

    tcc  -run hello.c

效果:hello.c不需编译,直接运行了

这个将C程序直接运行的功能,是TCC独有的,其它C编译器都不行。







三、更多例程


     tcc\examples 目录下有几个例程

   

 fib.c 是一个标准C语言例程,不多说 , 编译它:tcc fib.c

    hello_win.c 是一个Windows GUI例程, 编译它:tcc hello_win.c

 dll.c 是一个dll 例程,编译它:

  tcc -shared dll.c       //生成dll.dll文件
tiny_impdef dll.dll  // 生成dll.def文件
        tcc hello_dll.c dll.def  //hello_dll.c 是调用 dll.dll 的例程




四,用TCC编译TCC

     书上一直是这样说的: C语言是自己编译自己的, 就是说C语言的编译器是用C语言写的,把编译器的源代码编译一下,就成了C语言编译器

 做一下这个实验: 用TCC编译TCC源码

 

 首先下载TCC的源码,下载页: http://download.savannah.gnu.org/releases/tinycc, 下载源码:tcc-0.9.26.tar.bz2

    解压缩到  c:\tcc-0.9.26

    其中的文件主要有:

    tcc.c  主程序

                libtcc.c  c语言标准库




 写一个BAT批处理文件,用于处理编译过程,存盘到c:\tcc-0.9.26\build.bat, 文件内容如下:

@echo ====用TCC编译TCC====

@set /p VERSION= < VERSION
@echo>config.h #define TCC_VERSION "%VERSION%"

@echo 设置tcc为c语言编译器
@set CC=tcc
@set target=-DTCC_TARGET_PE -DTCC_TARGET_I386

@echo 编译工具软件:tiny_impdef.exe, tiny_libmaker.exe
@%CC% %target% win32/tools/tiny_impdef.c -o tiny_impdef.exe
@%CC% %target% win32/tools/tiny_libmaker.c -o tiny_libmaker.exe

@echo 编译libtcc.dll
@if not exist libtcc\nul mkdir libtcc
@%CC% %target% -w -shared -DLIBTCC_AS_DLL -DONE_SOURCE libtcc.c -o libtcc.dll -w
@tiny_impdef libtcc.dll -o libtcc/libtcc.def

@echo 编译tc.exe
@%CC% %target% tcc.c -o tc.exe -ltcc -Llibtcc


@echo 编译结束, 生成文件:  tc.exe






运行 build.bat, 结果如下

C:\tcc-0.9.26>build
====用TCC编译TCC====
设置tcc为c语言编译器
编译工具软件:tiny_impdef.exe, tiny_libmaker.exe
编译libtcc.dll
编译tc.exe
tcc.c:81: warning: assignment from incompatible pointer type
编译结束, 生成文件:  tc.exe




OK, 生成 tc.exe, 这个就是新的C语言编译器了

我让编译后生成的文件名是 tc.exe 而不是tcc.exe,  这样是为了方便与原始的编译器tcc.exe区分开




试用一下新的编译器tc.exe, 编译一下之前的 hello.c

 tc  hello.c

显示有错误:

hello.c:1: error: include file 'stdio.h' not found


为什么 stdio.h 没找到呢? 因为当前目录下的includes目录中没有stdio.h




新建一个目录    mkdir  c:\newtcc

把相关文件复制进去

copy c:\tcc-0.9.26\*.exe c:\newtcc

copy c:\tcc-0.9.26\libtcc.dll c:\newtcc


xcopy /E c:\tcc\include c:\newtcc\include\


xcopy /E c:\tcc\libtcc c:\newtcc\libtcc\


xcopy /E c:\tcc\lib c:\newtcc\lib\







再用新目录下的 tc.exe 作为编译器

c:\newtcc\tc.exe  hello.c


编译正确,生成 hello.exe

运行hello.exe, 则出现




hello, world




OK, 正确了

 

 

TCC研究(2) 把C语言当作脚本,解释执行,并嵌入各类程序,tcc脚本


热度3 评论 228
www.BkJia.Com  网友分享于:  2015-04-10 08:04:34     浏览数21067次 .



  

  





TCC研究(2) 把C语言当作脚本,解释执行,并嵌入各类程序,tcc脚本


Tiny C Compiler(简称TCC, 或Tiny CC)是世界上最小的C语言编译器。

TCC有一个突出的特点:就是可以把C语言当作脚本使用。试用记录如下:




首先,安装好TCC.   

在Windows下, 下载执行程序: tcc-0.9.26-win32-bin.zip。 解压到c:\tcc, 将c:\tcc添加到PATH目录中。

测试安装是否成功,在命令行窗口中打入命令   tcc -v , 看到TCC版本号即是成功




方式一: 以TCC解释执行C语言文本

解释执行,就是不编译,直接运行。

写一段C程序,存盘为 hello.c

#include <stdio.h>

int main(int argc, char *argv[]) {
   int i;
   printf("Hello, world\n");

   for(i=1; i<argc; i++)
     printf("argv[%d]=%s\n", i, argv);
}
在命令行窗口,打入命令   tcc -run hello.c

-run 的意思是立即执行

运行结果:

Hello, world




还可以通过命令行,向程序传入参数

在命令行窗口,打入命令   tcc -run hello.c param1 param2


运行结果(显示有两个传入参数):

Hello, world
argv[1]=param1
argv[2]=param2





方式二: 在Linux下,把C语言程序当作脚本运行,像SH脚本一样


首先,在Linux下安装TCC,我用的版本是Ubuntu 14.04

下载TCC源码,解压

cd ~

wget  http://download.savannah.gnu.org ... /tcc-0.9.26.tar.bz2

tar  xvf  tcc-0.9.26.tar.bz2

则生成一个子目录 tcc-0.9.26,进入目录,编译它

cd tcc-0.9.26

./configure

make

make install

完成后,输入命令  tcc -v .  如显示tcc版本号,表示成功。

用find命令查找一下 tcc 在哪?    find / -name tcc

发现tcc可执行文件安装在  /usr/local/bin


写一段C程序,存盘为 hello.c

#!/usr/local/bin/tcc -run
#include <stdio.h>

int main(int argc, char *argv[]) {
   printf("Hello, world\n");

}
第一行的 #!/usr/local/bin/tcc -run 是告诉操作系统,这是一个脚本,解释器是 /usr/local/bin/tcc
修改权限,将hello.c 转为可执行文件

   chmod +x hello.c

直接运行hello.c

./hello.c

运行结果:

hello,world

感觉不错, .c文件直接当脚本




对比一下, 编译后再执行的情况 ,把hello.c编译一下:    tcc hello.c   ,  将生成 a.out

运行a.out:  ./a.out

运行结果与脚本执行一样

由于有第一行的 #!/usr/local/bin/tcc -run, 用gcc编译hello.c会出错,用tcc编译就没问题


方式三:在程序中嵌入脚本功能,动态调用C语言脚本(这是TCC的精华之处)

嵌入脚本,就是让你的程序具有脚本功能,而且这个脚本还是C语言的。

tcc目录下的 examples\libtcc_test.c是一个示范程序。

我觉得示范程序不通用,于是自己编了一个通用的模块,两个文件:   cscript.c, cscript.h

先看 cscript.c,  定义了一个函数  run_script()

说明一下: libtcc.h 是 tcc提供的一个头文件,在tcc目录下复制过来的

#include <stdlib.h>
#include "libtcc.h" //TCC提供的头文件

/* 运行一个脚本,  启动脚本中指定的函数,返回该函数的运行结果值
* 脚本中的启动函数原型必须为: int func(int param)
* program是脚本全部内容, function_name是启动函数名称,param是传递给函数的参数
* tcc_path用于指定tcc所在目录,tcc_path设为时NULL表示不指定tcc目录
*/
int run_script(char *program, char *function_name, int param, char *tcc_path)
{
    TCCState *s; //TCC编译引擎
    int (*func)(int); //一个函数指针, 函数原型为:   int func(int param)
    int result;  //运行结果

    s = tcc_new(); //初始化TCC编译引擎
    if (!s) return -1; //初始化失败

    //tcc_path指定tcc所在的目录
    if (tcc_path!=NULL) tcc_set_lib_path(s, tcc_path);

    //指明编译结果写入内存,而不是存为文件
    tcc_set_output_type(s, TCC_OUTPUT_MEMORY);

        //如果编译失败,则退出
    if (tcc_compile_string(s, program) == -1) {
            tcc_delete(s); return -2;
    }

    //如果程序重定位失败,退出
    if (tcc_relocate(s, TCC_RELOCATE_AUTO) < 0) {
          tcc_delete(s); return -3;
    }

    //寻找名为function_name的入口函数
    func = tcc_get_symbol(s, function_name);
    if (!func) {  //找不到入口函数,则退出
        tcc_delete(s); return -4;
    }

    //运行入口函数,获得运行结果
    result = func(param);

    //结束TCC编译引擎
    tcc_delete(s);

    return result;
}

再看  cscript.h ,无非声明了一下run_script()这个函数。

#ifndef __CSCRIPT_H__
#define __CSCRIPT_H__

#ifdef __cplusplus
extern "C" {
#endif

/** 运行一个脚本,  启动脚本中指定的函数,返回该函数的运行结果值
* 脚本中的启动函数原型必须为: int func(int param)
* programe是脚本程序, function_name是启动函数,param是传递给函数的参数
* tcc_path用于指定tcc所在目录,tcc_path设为NULL时表示不指定tcc目录
*/
extern int run_script(char *program, char *function_name, int param, char *tcc_path);

#ifdef __cplusplus
}
#endif

#endif

好了, 编一个主程序,使用通用模块中的run_script()函数,每次通过命令行指定脚本文件,读出并运行。

假设主程序名为 cscript.exe

运行命令为:   cscript   <script_file>

主程序如下,存盘为 main.c

#include <stdio.h>
#include <stdlib.h>
#include "cscript.h" //通用模块头文件

int main(int argc, char *argv[])
{
        char *program = NULL; //脚本内容
        char *function_name = "script_main"; //启动函数名为script_main
        int  param = 888; //传入参数
        char *tcc_path = NULL;
        int  file_size;
        int  result; //返回结果
        FILE *fp;

        if (argc<=1) {
                printf("Usage: cscript <script_file>\n");
                return -1;
        }

        //第一个命令行参数是脚本文件名,打开它
        if ((fp=fopen(argv[1],"rb"))==NULL) {
                printf("Error open file %s\n", argv[1]);
                return -1;
        }

        //测出文件长度
        fseek(fp, 0L, SEEK_END);
        file_size = ftell(fp);
        //申请一个内存用于存放文件内容
        program = (char *)malloc(file_size+1);
        if (program==NULL) return -1;
        //将文件内容全部读入到 program中
        fseek(fp, 0L, SEEK_SET);
        if (fread(program, file_size, 1, fp)>0) {
                program[file_size]=0;
                result = run_script(program, function_name, param, tcc_path);//运行
                printf("result = %d\n", result);
        }
        free(program);
        fclose(fp);
}
这个程序比较简单,无非是从命令行第一个参数指定的文件中,读出内容,当作脚本执行。

脚本中需要指定一个启动函数,这个函数不是main(), 而是 int script_main(int param)

编译主程序(main.c 和 cscript.c), 假设tcc安装在c:\tcc目录下

tcc -llibtcc -Lc:\tcc main.c cscript.c -o cscript.exe

-llibtcc 表示链接 libtcc库 (大小写不要写错)

-Lc:\tcc 指定tcc目录(大小写不要写错)

-o cscript.exe 指定生成exe文件为 cscript.exe

编译成功,没有任何提示。目录下生成 cscript.exe




好了,写一个脚本(内容如下),存盘为 test1.txt

#include <stdio.h>

int script_main(int param)
{
   printf("it's in script main, param =%d\n", param);
   return param;
}

用刚才生成的 cscript.exe 直接运行 test1.txt

打入命令   cscript  test1.txt

运行结果:

it's in script main, param =888
result = 888


成功!!

这一次,我们不是使用tcc去运行脚本,而是采用刚才自己写的 cscript.exe程序去运行脚本。让自己的程序具有了脚本功能。

从TCC提供的example上看,TCC可以支持标准C, windows API调用,Linux动态库调用等,就是说,用脚本写系统程序、GUI,什么都行。

相当于远程分发程序。

如何应用,看想像力了。比如说: 程序从网站下载一个文本,运行,生成一个GUI,产生一个小人在桌面上跑来跑去……

再比如:大数据运算,100台计算机联网,某台机分发一个脚本,各机本地运算,返回结果给主机汇总,就是大数据的map-reduce算法嘛。

心有多远,就有多远

 

 类似资料: