Tiny C Compiler参考手册

韦原
2023-12-01

 

Tiny C Compiler参考手册

译者:gashero
原文: http://bellard.org/tcc/tcc-doc.html

1   简介

TinyCC (简称TCC)是一个小且很快的C编译器。不像其他C编译器,他可以自依赖:你不需要扩展汇编器或连接器,因为TCC已经为你准备好了。

TCC编译速度奇快,以至于Makefile都不是那么必要了。

TCC不仅仅支持ANSI C,还支持ISO C99标准和很多GNU C扩展,包括内联汇编。

TCC还可以用于C脚本,例如一段C代码可以像Perl或Python脚本那样运行。编译速度很快,有如可执行文件一样。

TCC还会自动生成所有C指针操作的内存边界检查。TCC做这些是无需补丁库的。

使用libtcc,你可以用TCC作为动态代码生成的后端。

TCC主要支持Linux和Windows的i386目标。还有个alpha状态的ARM(arm-tcc)和TMS320C67xx(c67-tcc)目标。更多信息关注 http://lists.gnu.org/archive/html/tinycc-devel/2003-10/msg00044.html

关于在Windows上的使用,查看 tcc-win32.txt

2   命令行选项

本手册基于Tiny C Compiler 0.9.25。

2.1   快速入门

使用:

tcc [options] [infile1 infile2 ...] [`-run' infile args ...]

TCC选项非常像gcc的选项,主要的不同在于TCC可以直接执行结果程序,并赋予它运行时的参数。

如下是一些逻辑的理解:

``tcc -run a.c``
编译 a.c 然后直接执行。

tcc -run a.c arg1

编译 a.c ,然后执行,并将arg1参数传递到 a.c main() 函数。

tcc a.c -run b.c arg1

编译 a.c b.c ,然后将他们连接到一起并执行。参数arg1作为结果程序 main() 的第一个参数。

tcc -o myprog a.c b.c

编译 a.c b.c ,然后连接成可执行文件 myprog

tcc -o myprog a.o b.o

连接两个目标文件,生成输出文件 myprog

tcc -c a.c

编译 a.c ,生成目标文件 a.o

tcc -c asmfile.S

asmfile.S 使用C预处理器和汇编器,并生成目标文件 asmfile.o

tcc -c asmfile.s

直接汇编,不做预处理,生成 asmfile.o

tcc -r -o ab.o a.c b.c

编译 a.c b.c ,并且把他们连接到一起,生成一个目标文件 ab.o

脚本:

TCC可以从脚本调用,有如shell脚本一样。你只需要将 #! /usr/local/bin/tcc -run 加到你的C源码开头即可:

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

int main() {
    printf("Hello World!\n");
    return 0;
}

TCC可以使用"-"选项,从标准输入读入C源码来替换输入文件,例如:

echo 'main(){puts("hello");}' | tcc -run -

2.2   选项摘要

常用选项:

-v

显示TCC版本,增加显示信息。

-c

生成目标文件。(必须给定"-o"选项)

-o outfile

输出目标文件、可执行文件、或者dll文件到outfile。

-Bdir

设置tcc内部库的搜索路径,缺省是 PREFIX/lib/tcc

-bench

输出编译统计。

-run source [args ...]

编译源文件并使用参数args来运行。为了给出更多选项到脚本,一些TCC选项可以在 -run 之后给出,空格分割,例如:

tcc "-run -L/usr/X11R6/lib -lX11" ex4.c

在脚本中使用如下头:

#! /usr/local/bin/tcc -run -L/usr/X11R6/lib -lX11
#include <stdlib.h>

int main(int argc, char** argv) {
    ...
}

预处理选项:

-Idir

指定附加的头文件路径。按照给出的顺序搜索。系统搜索路径总是排在最后。缺省系统头文件路径是: /usr/local/include /usr/include PREFIX/lib/tcc/include (PREFIX通常是 /usr 或者 /usr/local )。

-Dsym[=val]

定义预处理器符号 "sym" 到 val。如果val没有给出,则缺省值为1。这里也可以定义函数风格的宏,例如 -DF(a)=a+1

-Usym

删除预处理器定义的符号 sym

编译标识:

注意如下每个警告选项都可以换用 -fno- 前缀。

-funsigned-char

让char类型变成无符号的。

-fsigned-char

让char类型有符号。

-fno-common

不为未初始化数据生成普通符号。

-fleading-underscore

为每个C符号添加下划线前缀。

警告选项:

-w

禁用所有警告。

如下每个警告选项都可以换用 -Wno- 前缀。

-Wimplicit-function-declaration

警告所有隐含(implicit)函数。

-Wunsupported

警告所有不支持的GCC的功能。

-Wwrite-string

让字符串常量类型使用 const char * 而不是 char *

-Werror

发生警告时中断编译。

-Wall

激活所有警告,除了 -Werror -Wunsupported -Wwrite-strings

链接选项:

-Ldir

指定附加的静态库路径,用于 "-l" 链接选项。缺省库路径是 /usr/local/lib /usr/lib /lib

-lxxx

链接指定的动态库 libxxx.so 或静态库 libxxx.a 。搜索库路径是由 -L 选项提供的。

-shared

生成动态库,而不是可执行文件(必须使用"-o"选项)。(不确定)

-static

生成静态链接可执行文件(缺省是动态链接)(必须用"-o"选项)。(不确定)

-rdynamic

导出动态符号到动态连接器。这对于使用 dlopen() 打开的库访问可执行符号表很有用。

-r

生成对象文件包含所有的输入文件(必须用"-o"选项)。

-Wl,-Ttext,address

设置 .text 段的位置到address。

-Wl,--oformat,fmt

使用fmt作为输出格式。支持的格式包括:

  1. elf32-i386 :ELF格式(缺省)
  2. binary :二进制镜像(只用于可执行输出)
  3. coff :COFF格式(仅用于可执行输出到TMS320C67xx目标)

调试选项:

-g

生成运行时调试信息,以便得到运行时错误信息。给出无效指针,而不仅仅是段错误。

-b

生成检查内存分配和数组/指针边界检查的附加代码。自动包含 "-g" 选项。注意生成代码会又大又慢。

-bt N

显示N个调用者的回溯。与"-g"和"-b"使用时有用。

注意GCC选项"-Ox"、"-fx"、"-mx"会被忽略。

3   C语言支持

3.1   ANSI C

TCC实现了ANSI C的所有标准,包括结构体中的位字段和浮点数(long double、double,和float的完整支持)。

3.2   ISOC99扩展

TCC实现了新的C标准ISO C 99的很多特性。当前暂时不支持的包括:复数(complex)、虚数(imaginary)和变长数组。

当前实现的ISO C 99特性:

  • 64 bit的 long long 类型。

  • _Bool 布尔类型

  • __func__ 是一个字符串变量,包含了当前函数名字。

  • __VA_ARGS__ 可以用于函数样式的宏,如下定义后的 dprintf 就可以使用不定参数了:

    #define dprintf(level,__VA_ARGS__) printf(__VA_ARGS__)
  • 声明可以出现在代码块的任何地方,有如C++一样。

  • 数组和结构体/联合体元素,可以用任何方式初始化:

    struct {int x,y;} st[10]={[0].x=1, [0].y=2};
    int tab[10]={1,2,[5]=5,[9]=9};
  • 支持复合初始化。如下初始化了一个指向已经初始化过的数组的指针,对于结构体和字符串也可以:

    int *p=(int []){1,2,3};
  • 支持十六进制浮点数构造,如下两句一样:

    double d=0x1234p10;
    double d=4771840.0;
  • inline (内联)关键字会被忽略。

  • restrict (约束)关键字会被忽略。

3.3   GNU C 扩展

TCC实现了一部分GNU C扩展:

  • 数组赋值可以不用"=":

    int a[10]={[0] 1, [5] 2, 3, 4};
  • 结构体字段赋值可以不用标签:

    struct {int x, y;} st={x:1,y:1};
    //替代
    struct {int x, y;} st={.x=1, .y=1};
  • "\e" 是ASCII字符27。

  • case 范围:

    switch(a) {
    case 1...9:
        printf("range 1 to 9\n");
        break;
    default:
        printf("unexcepted\n");
        break;
    }

@waiting ...

3.4   TinyCC扩展

  • __TINYC__ 是一个预定义宏,值为1,用于指示使用了TCC。
  • 允许首行的 "#!" 指定脚本运行。
  • 支持二进制数字,例如 0b101 代表 5。
  • __BOUNDS_CHECKINT_ON 定义用于是否激活边界检查。

4   TinyCC汇编

自从版本0.9.16,TinyCC开始集成自己的汇编器了。TinyCC的汇编器支持gas-like语法(GNU汇编器)。你可以禁用汇编器支持,来得到一个更小的TinyCC可执行文件(C编译器并不依赖汇编器)。

4.1   语法

@waiting ...

4.2   表达式

@waiting ...

4.3   标签

@waiting ...

4.4   指令

@waiting ...

4.5   X86汇编器

@waiting ...

5   TinyCC连接器

5.1   ELF文件生成器

TCC可以在不需要其他连接器的情况下,直接输出重定位的ELF文件(目标文件),可执行ELF文件和动态ELF库。

动态ELF库可以直接输出,但是C编译器并不生成位置独立代码(PIC, Position independent Code)。这意味着TCC生成的动态库无法在进程间分解。

TCC连接器会清楚库里面的无引用对象。这在生成目标文件和库列表时直接跳过,所以指定目标文件和库文件的顺序很重要(对GNU ld也是一样的)。同样也不支持分组选项("--start-group"和"--end-group")。

5.2   ELF文件载入器

TCC无法载入ELF目标文件、静态库(.a文件)和动态库(.so文件)。

5.3   PE-i386文件生成器

TCC的Windows支持本地Win32可执行文件格式(PE-i386)。他可以生成EXE文件(控制台的和GUI的)和DLL文件。

了解在Windows上的使用,查看 tcc-win32.txt

5.4   GNU连接器脚本

因为很多Linux系统的动态库使用GNU ld链接脚本,所以TCC连接器也支持GNU ld脚本的子集。

GROUP和FILE命令是支持的,OUTPUT_FORMAT和TARGET则被忽略。

例如 /usr/lib/libc.so

/* GNU ld script */
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )

6   TinyCC内存与边界检查

该功能使用 "-b" 选项激活。

主意指针大小是不变的,而包含边界检查的代码生成与无检查的代码是全兼容的。当有来自无检查代码的指针时,会假设其有效。因此混用的代码之间也可以正常工作。

关于更多方法的信息请关注 http://www.doc.ic.ac.uk/~phjk/BoundsChecking.html

这里是一些捕捉错误的例子:

标准字符串函数处理无效的范围:

{
    char tab[10];
    memset(tab,0,11);
}

对本地和全局数组的越界错误:

{
    int tab[10];
    for(i=0;i<11;i++) {
        sum+=tab[i];
    }
}

由malloc生成的数据的越界错误:

{
    int *tab;
    tab=malloc(20*sizeof(int));
    for(i=0;i<21;i++) {
        sum+=tab4[i];
    }
    free(tab);
}

访问已经释放了的内存:

{
    int *tab;
    tab=malloc(20*sizeof(int));
    free(tab);
    for(i=0;i<20;i++) {
        sum+=tab4[i];
    }
}

二次释放:

{
    int *tab;
    tab=malloc(20*sizeof(int));
    free(tab);
    free(tab);
}

7   libtcc库

libtcc库允许你把TCC作为动态代码生成的后端。

阅读 libtcc.h 了解API。阅读 libtcc_test.c 看简单的例子。

这个主意在于你可以把C字符串直接通过libtcc编译。然后可以存取任意的全局符号。

7.1   libtcc API

TCCState *tcc_new(void)

创建新的TCC编译上下文。

void tcc_delete(TCCState *s)

释放TCC编译上下文。

void tcc_enable_debug(TCCState *s)

在生成代码中添加调试信息。

void tcc_set_error_func(TCCState *s, void *error_opaque, void (*error_func)(void *opaque, const char *msg))

设置发生错误/警告时的回调函数。

int tcc_set_warning(TCCState *s, const char *warning_name, int value)

设置和重设警告。

下面是预处理函数。

int tcc_add_include_path(TCCState *s, const char *pathname)

添加头文件搜索路径。

int tcc_add_sysinclude_path(TCCState *s, const char *pathname)

添加系统头文件搜索路径。

void tcc_define_symbol(TCCState *s, const char *sym, const char *value)

定义预处理器符号 "sym" ,可以添加可选的值。

void tcc_undefine_symbol(TCCState *s, const char *sym)

取消预处理器符号 "sym" 的定义。

如下是编译函数。

int tcc_add_file(TCCState *s, const char *filename)

添加一个文件,可以是C文件、dll、对象、库或者ld脚本。错误返回-1。

int tcc_compile_string(TCCState *s, const char *buf)

编译作为C源码的字符串,错误返回非0。

链接命令。

输出类型,必须在所有编译之前定义:

  1. TCC_OUTPUT_MENORY :输出到内存,缺省值
  2. TCC_OUTPUT_EXE :输出到可执行文件
  3. TCC_OUTPUT_DLL :输出动态库
  4. TCC_OUTPUT_OBJ :输出目标文件
  5. TCC_OUTPUT_PREPROCESS :输出预处理文件(内部使用)

int tcc_set_output_type(TCCState *s, int output_type)

设置输出类型。

可执行文件格式:

  1. TCC_OUTPUT_FORMAT_ELF :输出格式ELF,缺省值
  2. TCC_OUTPUT_FORMAT_BINARY :二进制镜像
  3. TCC_OUTPUT_FORMAT_COFF :COFF

int tcc_add_library_path(TCCState *s, const char *pathname)

等效于 "-Lpath" 选项。

int tcc_add_library(TCCState *s, const char *libraryname)

等同于用 "-lxxx" 选项添加链接库。

int tcc_add_symbol(TCCState *s, const char *name, void *val)

添加符号到编译过的程序。

int tcc_output_file(TCCState *s, const char *filename)

输出可执行文件、库或目标文件,在此之前别调用 tcc_relocate()

int tcc_run(TCCState *s, int argc, char **argv)

链接和运行 main() 函数,并返回值。在此之前别调用 tcc_relocate()

int tcc_relocate(TCCState *s1, void *ptr)

拷贝代码到内存传递给调用者,并做重定位(必须在 tcc_get_symbol() 之前调用)。如果出错返回-1且在ptr是NULL时需要大小。

void *tcc_get_symbol(TCCState *s, const char *name)

返回符号的值或找不到时返回NULL。

void tcc_set_lib_path(TCCState *s, const char *path)

设置运行时的CONFIG_TCCDIR。

7.2   libtcc的例子

tests/libtcc_test.c

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "libtcc.h"

/*这个函数将会被生成的代码所调用*/
int add(int a, int b) {
    return a+b;
}

char my_program[] =
"int fib(int n) {\n"
"    if (n<=2)\n"
"        return 1;n"
"    else\n"
"        return fib(n-1)+fib(n-2);\n"
"}\n"
"int foo(int n) {\n"
"    printf(\"Hello World!\\n);\n"
"    return 0;\n"
"}\n";

int main(int argc, char **argv) {
    TCCState *s;
    int (*func)(int);
    void *mem;
    int size;

    s=tcc_new();
    if (!s) {
        fprintf(stderr, "Could not create tcc state\n");
        exit(1);
    }
    /*如果tcclib.h和libtcc1.a尚未安装,寻找他们*/
    if (argc==2 && !memcmp(argv[1],"lib_path=",9))
        tcc_set_lib_path(s,argv[1]+9);

    /*必须在任何编译之前设置输出类型*/
    tcc_set_output_type(s,TCC_OUTPUT_MEMORY);
    if (tcc_compile_string(s,my_program) == -1)
        return 1;

    /*添加add()函数允许动态生成代码调用,还可以用 tcc_add_dll() 来用。*/
    tcc_add_symbol(s,"add",add);
    /*获取代码大小*/
    size=tcc_relocate(s,NULL);
    if (size== -1)
        return 1;
    /*重定位内存并拷贝代码*/
    mem=malloc(size);
    tcc_relocate(s,mem);
    /*获取入口符号*/
    func=tcc_get_symbol(s,"foo");
    if (!func)
        return 1;
    /*删除状态*/
    tcc_delete(s);
    /*运行代码*/
    func(32);
    free(mem);
    return 0;
}

8   开发者指南

本章给出一些介绍用以了解TCC的工作方式。如果不关心修改TCC的代码,可以直接跳过。

@waiting ...

8.1   文件读取

@waiting ...

8.2   词法(lexer)

@waiting ...

8.4   类型

@waiting ...

8.5   符号

@waiting ...

8.6   段

@waiting ...

8.7   代码生成

8.7.1   介绍

TCC代码生成直接生成连接过的二进制代码。尽管不太常用(查看gcc生成文本汇编代码的例子),但是速度却很快且有些复杂。

TCC代码生成器是基于寄存器的。优化限于表达式级别。表达式的中间表示方式不保存,除非其值在 "value stack" 中。

在x86上,使用了3个临时寄存器。当需要更多寄存器时,一个寄存器被切分到栈上的临时变量中。

8.7.2   数值栈

@waiting ...

8.8   优化

@waiting ...

9   关于本文档

@waiting ...

 类似资料: