Tiny C Compiler参考手册
gashero |
http://bellard.org/tcc/tcc-doc.html |
目录
- 1 简介
- 2 命令行选项
- 2.1 快速入门
- 2.2 选项摘要
- 3 C语言支持
- 3.1 ANSI C
- 3.2 ISOC99扩展
- 3.3 GNU C 扩展
- 3.4 TinyCC扩展
- 4 TinyCC汇编
- 4.1 语法
- 4.2 表达式
- 4.3 标签
- 4.4 指令
- 4.5 X86汇编器
- 5 TinyCC连接器
- 5.1 ELF文件生成器
- 5.2 ELF文件载入器
- 5.3 PE-i386文件生成器
- 5.4 GNU连接器脚本
- 6 TinyCC内存与边界检查
- 7 libtcc库
- 7.1 libtcc API
- 7.2 libtcc的例子
- 8 开发者指南
- 8.1 文件读取
- 8.2 词法(lexer)
- 8.3 解析器(parser)
- 8.4 类型
- 8.5 符号
- 8.6 段
- 8.7 代码生成
- 8.7.1 介绍
- 8.7.2 数值栈
- 8.7.3 操作数值栈
- 8.7.4 CPU相关的代码生成
- 8.8 优化
- 9 关于本文档
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 #includeint 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 #includeint 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作为输出格式。支持的格式包括:
- elf32-i386 :ELF格式(缺省)
- binary :二进制镜像(只用于可执行输出)
- 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。
链接命令。
输出类型,必须在所有编译之前定义:
- TCC_OUTPUT_MENORY :输出到内存,缺省值
- TCC_OUTPUT_EXE :输出到可执行文件
- TCC_OUTPUT_DLL :输出动态库
- TCC_OUTPUT_OBJ :输出目标文件
- TCC_OUTPUT_PREPROCESS :输出预处理文件(内部使用)
int tcc_set_output_type(TCCState *s, int output_type)
设置输出类型。
可执行文件格式:
- TCC_OUTPUT_FORMAT_ELF :输出格式ELF,缺省值
- TCC_OUTPUT_FORMAT_BINARY :二进制镜像
- 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#include #include #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.3 解析器(parser)
@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.7.3 操作数值栈
@waiting ...
8.7.4 CPU相关的代码生成
@waiting ...
8.8 优化
@waiting ...
9 关于本文档
@waiting ...