### Tiny C 编译器参考文档
**目录**
1. 引言
2. 命令行调用
2.1 快速入门
2.2 选项概述
3. C 语言支持
3.1 ANSI C
3.2 ISO C99 扩展
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 库
8. 开发者指南
8.1 文件读取
8.2 词法分析器
8.3 语法分析器
8.4 类型
8.5 符号
8.6 段
8.7 代码生成
8.7.1 引言
8.7.2 值栈
8.7.3 操作值栈
8.7.4 依赖于 CPU 的代码生成
8.8 已完成的优化
**概念索引**
---
### Tiny C 编译器参考文档
本手册记录了 Tiny C 编译器(TCC)0.9.27 版本。
#### 1. 引言
TinyCC(简称 TCC)是一个小巧但超快的 C 编译器。与其他 C 编译器不同,TCC 是自依赖的:无需外部汇编器或链接器,TCC 能够自行完成这些工作。
TCC 编译速度极快,即使对于大型项目,也可能无需使用 Makefile。
TCC 不仅支持 ANSI C,还支持大部分 ISO C99 标准以及许多 GNU C 扩展,包括内联汇编。
TCC 还可用于创建 C 脚本,即像 Perl 或 Python 脚本一样运行的 C 源代码片段。编译速度快到足以让脚本运行速度接近可执行文件。
TCC 还能自动生成内存和边界检查(参见“边界检查”),同时允许所有 C 指针操作。即使使用未修补的库,TCC 也能进行这些检查。
通过 libtcc 库,TCC 可作为动态代码生成的后台(参见“libtcc”)。
TCC 主要支持 Linux 和 Windows 上的 i386 目标平台。ARM(arm-tcc)和 TMS320C67xx(c67-tcc)目标平台有 alpha 版本支持。有关 ARM 端口的更多信息,请访问:http://lists.gnu.org/archive/html/tinycc-devel/2003-10/msg00044.html。
在 Windows 上使用 TCC,请参见 tcc-win32.txt。
---
#### 2. 命令行调用
##### 2.1 快速入门
用法:`tcc [选项] [输入文件1 输入文件2…] [-run 输入文件 参数…]`
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`
链接 a.o 和 b.o,生成可执行文件 myprog。
- `tcc -c a.c`
编译 a.c,生成目标文件 a.o。
- `tcc -c asmfile.S`
使用 C 预处理器预处理并汇编 asmfile.S,生成目标文件 asmfile.o。
- `tcc -c asmfile.s`
汇编(不预处理)asmfile.s,生成目标文件 asmfile.o。
- `tcc -r -o ab.o a.c b.c`
编译 a.c 和 b.c,链接并生成目标文件 ab.o。
**脚本化:**
TCC 可通过脚本调用,就像 shell 脚本一样。只需在 C 源代码开头添加 `#!/usr/local/bin/tcc -run`:
```c
#!/usr/local/bin/tcc -run
#include <stdio.h>
int main()
{
printf("Hello World\n");
return 0;
}
```
TCC 可以从标准输入读取 C 源代码,当使用 `-` 代替输入文件时。例如:
```bash
echo 'main(){puts("hello");}' | tcc -run -
```
##### 2.2 选项概述
**通用选项:**
- `-c`
生成目标文件。
- `-o outfile`
将目标文件、可执行文件或 DLL 输出到 outfile。
- `-run source [args...]`
编译源文件 source 并运行,带命令行参数 args。为了给脚本传递多个参数,可以在 -run 后添加多个 TCC 选项,用空格分隔:
例如:`tcc "-run -L/usr/X11R6/lib -lX11" ex4.c`
在脚本中,这对应以下头部:
```c
#!/usr/local/bin/tcc -run -L/usr/X11R6/lib -lX11
```
- `-v`
显示 TCC 版本。
- `-vv`
显示包含的文件。若单独使用,打印搜索目录。`-vvv` 还会显示尝试的路径。
- `-bench`
显示编译统计信息。
**预处理器选项:**
- `-Idir`
指定额外的包含路径,按指定顺序搜索。系统包含路径(/usr/local/include、/usr/include、PREFIX/lib/tcc/include)总是在之后搜索。
- `-Dsym[=val]`
定义预处理器符号 sym 为 val。若未提供 val,则值为 1。支持函数式宏:`-DF(a)=a+1`。
- `-Usym`
取消定义预处理器符号 sym。
- `-E`
仅预处理,输出到 stdout 或指定文件(使用 -o)。
**编译选项:**
(注:以下每个选项都有以 -fno- 开头的否定形式)
- `-funsigned-char`
将 char 类型视为无符号。
- `-fsigned-char`
将 char 类型视为有符号。
- `-fno-common`
不为未初始化的数据生成公共符号。
- `-fleading-underscore`
在每个 C 符号前添加下划线。
- `-fms-extensions`
允许 Microsoft C 编译器的语言扩展。目前假设嵌套命名结构体声明若无标识符则视为未命名。
- `-fdollars-in-identifiers`
允许标识符中使用美元符号。
**警告选项:**
- `-w`
禁用所有警告。
(注:以下警告选项也有以 -Wno- 开头的否定形式)
- `-Wimplicit-function-declaration`
警告隐式函数声明。
- `-Wunsupported`
警告 TCC 忽略的不支持的 GCC 特性。
- `-Wwrite-strings`
将字符串常量视为 const char * 而非 char *。
- `-Werror`
如果发出警告,则中止编译。
- `-Wall`
激活除 -Werror、-Wunsupported 和 -Wwrite-strings 外的所有警告。
**链接器选项:**
- `-Ldir`
为 -l 选项指定额外的静态库路径。默认库路径为 /usr/local/lib、/usr/lib 和 /lib。
- `-lxxx`
链接动态库 libxxx.so 或静态库 libxxx.a。使用 -L 选项和 LIBRARY_PATH 变量指定的路径搜索。
- `-Bdir`
设置 TCC 内部库(及包含文件)的路径(默认 PREFIX/lib/tcc)。
- `-shared`
生成共享库而非可执行文件。
- `-soname name`
设置共享库运行时的名称。
- `-static`
生成静态链接的可执行文件(默认生成共享链接的可执行文件)。
- `-rdynamic`
向动态链接器导出全局符号。用于 dlopen() 打开的库需要访问可执行文件符号时。
- `-r`
合并所有输入文件生成目标文件。
- `-Wl,-rpath=path`
将自定义动态库搜索路径加入可执行文件。
- `-Wl,--enable-new-dtags`
将自定义动态库搜索路径加入可执行文件时,使用新的 ELF 动态标签 DT_RUNPATH 而非旧的 DT_RPATH。
- `-Wl,--oformat=fmt`
使用 fmt 作为输出格式。支持的格式包括:
- `elf32-i386`:ELF 输出格式(默认)
- `binary`:二进制映像(仅用于可执行输出)
- `coff`:COFF 输出格式(仅用于 TMS320C67xx 目标的可执行输出)
- `-Wl,-subsystem=console/gui/wince/...`
设置 PE(Windows)可执行文件的类型。
- `-Wl,-[Ttext=# | section-alignment=# | file-alignment=# | image-base=# | stack=#]`
修改可执行文件布局。
- `-Wl,-Bsymbolic`
设置 DT_SYMBOLIC 标签。
- `-Wl,-(no-)whole-archive`
开启/关闭链接所有存档中的对象。
**调试器选项:**
- `-g`
生成运行时调试信息,以便获得清晰的运行时错误信息,例如:`test.c:68: in function 'test5()': dereferencing invalid pointer`,而非简单的“Segmentation fault”。
- `-b`
生成额外的支持代码以检查内存分配和数组/指针边界。隐含 -g。生成的代码会较慢且更大。
注意:-b 目前仅在 i386 上使用 libtcc 时可用。
- `-bt N`
在堆栈跟踪中显示 N 个调用者。与 -g 或 -b 一起使用很有效。
**其他选项:**
- `-MD`
生成包含依赖关系的 Makefile 片段。
- `-MF depfile`
将 -MD 的输出写入 depfile。
- `-print-search-dirs`
打印配置的安装目录及 TCC 搜索的库和包含目录列表。
- `-dumpversion`
打印版本号。
**目标特定选项:**
- `-mms-bitfields`
使用与 MSVC 一致的位域对齐算法。默认使用 gcc 的算法。
- `-mfloat-abi`(仅 ARM)
选择浮点 ABI。可能值:softfp 和 hard。
- `-mno-sse`
在 x86_64 上不使用 SSE 寄存器。
- `-m32, -m64`
传递命令行到 i386/x86_64 交叉编译器。
注意:GCC 的 -Ox、-fx 和 -mx 选项被忽略。
**影响 TCC 操作的环境变量:**
- `CPATH`, `C_INCLUDE_PATH`
以冒号分隔的包含文件搜索目录列表,-I 指定的目录优先。
- `LIBRARY_PATH`
以冒号分隔的库搜索目录列表,-L 指定的目录优先。
---
#### 3. C 语言支持
##### 3.1 ANSI C
TCC 实现了完整的 ANSI C 标准,包括结构体位域和浮点数(完全支持 long double、double 和 float)。
##### 3.2 ISO C99 扩展
TCC 实现了许多 ISO C99 标准特性。目前缺少的特性包括:复数和虚数。
已实现的 ISO C99 特性包括:
- **变长数组**。
- **64 位 long long 类型**完全支持。
- **布尔类型 _Bool**支持。
- **`__func__`** 是一个包含当前函数名的字符串变量。
- **可变参数宏**:支持 `__VA_ARGS__` 用于函数式宏:
```c
#define dprintf(level, __VA_ARGS__) printf(__VA_ARGS__)
```
dprintf 可接受可变数量的参数。
- **声明可在块中任意位置出现**(如 C++)。
- **数组和结构体/联合体元素可使用指定符以任意顺序初始化**:
```c
struct { int x, y; } st[10] = { [0].x = 1, [0].y = 2 };
int tab[10] = { 1, 2, [5] = 5, [9] = 9 };
```
- **复合初始化器**支持:
```c
int *p = (int []){ 1, 2, 3 };
```
用于初始化指向已初始化数组的指针。结构体和字符串同理。
- **十六进制浮点常量**支持:
```c
double d = 0x1234p10;
```
等价于:
```c
double d = 4771840.0;
```
- **`inline` 关键字**被忽略。
- **`restrict` 关键字**被忽略。
##### 3.3 GNU C 扩展
TCC 实现了一些 GNU C 扩展:
- **数组指定符无需 '='**:
```c
int a[10] = { [0] 1, [5] 2, 3, 4 };
```
- **结构体字段指定符可为标签**:
```c
struct { int x, y; } st = { x: 1, y: 1 };
```
而非:
```c
struct { int x, y; } st = { .x = 1, .y = 1 };
```
- **\e** 表示 ASCII 字符 27。
- **case 范围**:在 case 中可以使用范围:
```c
switch(a) {
case 1 ... 9:
printf("range 1 to 9\n");
break;
default:
printf("unexpected\n");
break;
}
```
- **`__attribute__` 关键字**用于指定变量或函数属性。支持的属性包括:
- `aligned(n)`:将变量或结构体字段对齐到 n 字节(n 必须为 2 的幂)。
- `packed`:强制变量或结构体字段对齐为 1。
- `section(name)`:将函数或数据生成到指定汇编段 name。
- `unused`:指定变量或函数未使用。
- `cdecl`:使用标准 C 调用约定(默认)。
- `stdcall`:使用类似 Pascal 的调用约定。
- `regparm(n)`:使用快速 i386 调用约定,n 为 1 到 3,分别将前 n 个参数放入寄存器 %eax、%edx 和 %ecx。
- `dllexport`:从 DLL/可执行文件中导出函数(仅限 Win32)。
示例:
```c
int a __attribute__ ((aligned(8), section(".mysection")));
```
将变量 a 对齐到 8 字节并放入 .mysection 段。
```c
int my_add(int a, int b) __attribute__ ((section(".mycodesection")))
{
return a + b;
}
```
将函数 my_add 生成到 .mycodesection 段。
- **GNU 风格的可变参数宏**:
```c
#define dprintf(fmt, args...) printf(fmt, ## args)
dprintf("no arg\n");
dprintf("one arg %d\n", 1);
```
- **`__FUNCTION__`** 被解释为 C99 的 `__func__`(与 GNUC 的字符串字面量语义略有不同)。
- **`__alignof__`** 可像 sizeof 一样获取类型或表达式的对齐方式。
- **`typeof(x)`** 返回 x 的类型,x 可以是表达式或类型。
- **计算型 goto**:`&&label` 返回指向 goto 标签 label 的 void * 指针,`goto *expr` 可跳转到 expr 结果的指针。
- **内联汇编**:使用 asm 指令:
```c
static inline void * my_memcpy(void * to, const void * from, size_t n)
{
int d0, d1, d2;
__asm__ __volatile__(
"rep ; movsl\n\t"
"testb $2,%b4\n\t"
"je 1f\n\t"
"movsw\n"
"1:\ttestb $1,%b4\n\t"
"je 2f\n\t"
"movsb\n"
"2:"
: "=&c" (d0), "=&D" (d1), "=&S" (d2)
:"0" (n/4), "q" (n),"1" ((long) to),"2" ((long) from)
: "memory");
return (to);
}
```
TCC 包含自己的 x86 内联汇编器,使用类似 gas(GNU 汇编器)的语法,无需生成中间文件。支持 GCC 3.x 命名操作数。
- **`__builtin_types_compatible_p()`** 和 `__builtin_constant_p()`** 支持。
- **`#pragma pack`** 支持,用于 Win32 兼容性。
##### 3.4 TinyCC 扩展
- **`__TINYC__`** 是一个预定义宏,指示使用 TCC。
- 行首的 `#!` 被忽略,以支持脚本化。
- 支持二进制数字(例如 `0b101` 表示 5)。
- 如果启用了边界检查,定义 `__BOUNDS_CHECKING_ON`。
---
#### 4. TinyCC 汇编器
从 0.9.16 版本开始,TCC 集成了自己的汇编器,支持类似 gas(GNU 汇编器)的语法。若需更小的 TCC 可执行文件,可禁用汇编器支持(C 编译器不依赖汇编器)。
TCC 汇编器用于处理 .S(C 预处理汇编)和 .s 扩展名的文件,以及处理使用 asm 关键字的 GNU 内联汇编。
##### 4.1 语法
TCC 汇编器支持大部分 gas 语法。标记与 C 相同。
- 支持 C 和 C++ 风格的注释。
- 标识符与 C 相同,因此不能使用 '.' 或 '$'。
- 仅支持 32 位整数。
##### 4.2 表达式
- 支持十进制、八进制和十六进制整数。
- 一元运算符:`+`、`-`、`~`。
- 二元运算符(按优先级递减):
- `*`、(`/`、`%`
- `&`、`|`、`^`
- `+`、`-`
- 值可以是绝对数字或标签加偏移量。除 `+` 和 `-` 外,所有运算符接受绝对值。`+` 或 `-` 可用于为标签添加偏移量。`-` 仅支持两个相同标签或在同一段中定义的标签。
##### 4.3 标签
- 所有标签视为局部标签,除非未定义。
- 数字标签可作为类似 gas 的局部标签,可在同一源文件中定义多次。使用 `b`(后向)或 `f`(前向)作为后缀引用:
```asm
1:
jmp 1b /* 跳转到前面的 '1' 标签 */
jmp 1f /* 跳转到后面的 '1' 标签 */
1:
```
##### 4.4 指令
所有指令以 '.' 开头。支持以下指令:
- `.align n[,value]`
- `.skip n[,value]`
- `.space n[,value]`
- `.byte value1[,...]`
- `.word value1[,...]`
- `.short value1[,...]`
- `.int value1[,...]`
- `.long value1[,...]`
- `.quad immediate_value1[,...]`
- `.globl symbol`
- `.global symbol`
- `.section section`
- `.text`
- `.data`
- `.bss`
- `.fill repeat[,size[,value]]`
- `.org n`
- `.previous`
- `.string string[,...]`
- `.asciz string[,...]`
- `.ascii string[,...]`
##### 4.5 X86 汇编器
支持所有 X86 操作码,仅支持 ATT 语法(源操作数在前,目标操作数在后)。若未指定大小后缀,TCC 会根据操作数大小猜测。
目前支持 MMX 操作码,但不支持 SSE 操作码。
---
#### 5. TinyCC 链接器
##### 5.1 ELF 文件生成
TCC 可直接输出可重定位的 ELF 文件(目标文件)、可执行 ELF 文件和动态 ELF 库,无需依赖外部链接器。
动态 ELF 库可以输出,但 TCC 生成的动态库代码不是位置无关代码(PIC),因此无法在多个进程间共享代码。
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 系统上的动态库(如 /usr/lib/libc.so)实际上是 GNU ld 链接脚本,TCC 链接器也支持 GNU ld 脚本的子集。
支持 `GROUP` 和 `FILE` 命令,忽略 `OUTPUT_FORMAT` 和 `TARGET`。
示例(来自 /usr/lib/libc.so):
```ld
/* GNU ld 脚本
使用共享库,但某些函数仅在静态库中,因此其次尝试静态库。 */
GROUP ( /lib/libc.so.6 /usr/lib/libc_nonshared.a )
```
---
#### 6. TinyCC 内存和边界检查
此功能通过 -b 选项激活(参见“命令行调用”)。
注意:指针大小不变,带边界检查的代码与未检查代码完全兼容。来自未检查代码的指针被假定为有效。即使是非常复杂的带有类型转换的 C 代码也应正常工作。
有关此方法背后的更多信息,请参见:http://www.doc.ic.ac.uk/~phjk/BoundsChecking.html。
以下是捕获的错误示例:
- **标准字符串函数的无效范围**:
```c
{
char tab[10];
memset(tab, 0, 11);
}
```
- **全局或局部数组的越界错误**:
```c
{
int tab[10];
for(i=0;i<11;i++) {
sum += tab[i];
}
}
```
- **malloc 分配数据的越界错误**:
```c
{
int *tab;
tab = malloc(20 * sizeof(int));
for(i=0;i<21;i++) {
sum += tab4[i];
}
free(tab);
}
```
- **访问已释放的内存**:
```c
{
int *tab;
tab = malloc(20 * sizeof(int));
free(tab);
for(i=0;i<20;i++) {
sum += tab4[i];
}
}
```
- **重复释放**:
```c
{
int *tab;
tab = malloc(20 * sizeof(int));
free(tab);
free(tab);
}
```
---
#### 7. libtcc 库
libtcc 库使 TCC 可作为动态代码生成的后台。
请阅读 libtcc.h 以了解 API 概览,阅读 libtcc_test.c 以查看简单示例。
其思想是将包含要编译程序的 C 字符串直接传递给 libtcc。然后可以访问定义的任何全局符号(函数或变量)。
---
#### 8. 开发者指南
本章提供了一些了解 TCC 工作原理的提示。如果不打算修改 TCC 代码,可以跳过。
##### 8.1 文件读取
`BufferedFile` 结构包含读取文件所需的上下文,包括当前行号。`tcc_open()` 打开新文件,`tcc_close()` 关闭文件。`inp()` 返回下一个字符。
##### 8.2 词法分析器
- `next()` 读取当前文件的下一个标记。
- `next_nomacro()` 读取下一个标记,不进行宏扩展。
- `tok` 包含当前标记(参见 TOK_xxx 常量)。标识符和关键字也是标记。
- `tokc` 包含标记的附加信息(例如,数字或字符串标记的常量值)。
##### 8.3 语法分析器
语法分析器是硬编码的(无需使用 yacc)。仅进行一次遍历,例外情况包括:
- 对于未知大小的初始化数组,第一次遍历用于计数元素。
- 对于参数按逆序求值的架构,第一次遍历用于反转参数顺序。
##### 8.4 类型
类型存储在单个 int 变量中。这是 TCC 早期开发时的选择,当时 TCC 更简单。现在可能不是最佳方案。
类型定义如下:
```c
#define VT_INT 0 /* 整数类型 */
#define VT_BYTE 1 /* 有符号字节类型 */
#define VT_SHORT 2 /* 短整数类型 */
#define VT_VOID 3 /* void 类型 */
#define VT_PTR 4 /* 指针 */
#define VT_ENUM 5 /* 枚举定义 */
#define VT_FUNC 6 /* 函数类型 */
#define VT_STRUCT 7 /* 结构体/联合体定义 */
#define VT_FLOAT 8 /* IEEE 浮点数 */
#define VT_DOUBLE 9 /* IEEE 双精度 */
#define VT_LDOUBLE 10 /* IEEE 长双精度 */
#define VT_BOOL 11 /* ISO C99 布尔类型 */
#define VT_LLONG 12 /* 64 位整数 */
#define VT_LONG 13 /* 长整数(仅在解析期间使用) */
#define VT_BTYPE 0x000f /* 基本类型掩码 */
#define VT_UNSIGNED 0x0010 /* 无符号类型 */
#define VT_ARRAY 0x0020 /* 数组类型(也有 VT_PTR) */
#define VT_VLA 0x20000 /* 变长数组类型(也有 VT_PTR 和 VT_ARRAY) */
#define VT_BITFIELD 0x0040 /* 位域修饰符 */
#define VT_CONSTANT 0x0800 /* const 修饰符 */
#define VT_VOLATILE 0x1000 /* volatile 修饰符 */
#define VT_DEFSIGN 0x2000 /* 有符号类型 */
#define VT_STRUCT_SHIFT 18 /* 结构体/枚举名称移位(剩余 14 位) */
```
- 当需要引用其他类型(如指针、函数、结构体)时,使用 32 - VT_STRUCT_SHIFT 高位存储标识符引用。
- `VT_UNSIGNED` 标志可用于 char、short、int 和 long long。
- 数组被视为带有 `VT_ARRAY` 标志的 `VT_PTR`。变长数组被视为特殊数组,使用 `VT_VLA` 标志。
- `VT_BITFIELD` 标志可用于 char、short、int 和 long long。若设置,则位域位置存储在 VT_STRUCT_SHIFT 到 VT_STRUCT_SHIFT + 5 位,位域大小存储在 VT_STRUCT_SHIFT + 6 到 VT_STRUCT_SHIFT + 11 位。
- `VT_LONG` 仅在解析期间使用。
解析期间,对象的存储方式也存储在类型整数中:
```c
#define VT_EXTERN 0x00000080 /* 外部定义 */
#define VT_STATIC 0x00000100 /* 静态变量 */
#define VT_TYPEDEF 0x00000200 /* typedef 定义 */
#define VT_INLINE 0x00000400 /* 内联定义 */
#define VT_IMPORT 0x00004000 /* Win32:从 DLL 导入的外部数据 */
#define VT_EXPORT 0x00008000 /* Win32:从 DLL 导出的数据 */
#define VT_WEAK 0x00010000 /* Win32:从 DLL 导出的数据 */
```
##### 8.5 符号
所有符号存储在哈希符号栈中。每个符号栈包含 Sym 结构。
- `Sym.v` 包含符号名称(标识符也是标记,无需存储字符串)。
- `Sym.t` 提供符号类型。
- `Sym.r` 通常是存储对应变量的寄存器。
- `Sym.c` 通常是与符号相关的常量,如普通符号的地址,或数组符号的条目数。变长数组类型使用 `Sym.c` 作为栈上存储运行时 sizeof 的位置。
定义了四个主要符号栈:
- `define_stack`:用于宏(#defines)。
- `global_stack`:用于全局变量、函数和类型。
- `local_stack`:用于局部变量、函数和类型。
- `global_label_stack`:用于局部标签(用于 goto)。
- `label_stack`:用于 GCC 块局部标签(参见 __label__ 关键字)。
- `sym_push()` 用于在局部符号栈中添加新符号。若无活动局部符号栈,则添加到全局符号栈。
- `sym_pop(st,b)` 从符号栈 st 弹出符号,直到符号 b 位于栈顶。若 b 为 NULL,则清空栈。
- `sym_find(v)` 返回与标识符 v 关联的符号。先从顶部到底部搜索局部栈,再搜索全局栈。
##### 8.6 段
生成的代码和数据写入段中。`Section` 结构包含给定段的必要信息。`new_section()` 创建新段。假设每个段具有 ELF 文件语义。
预定义段包括:
- `text_section`:包含生成代码。`ind` 包含代码段中的当前位置。
- `data_section`:包含初始化数据。
- `bss_section`:包含未初始化数据。
- `bounds_section`、`lbounds_section`:在启用边界检查时使用。
- `stab_section`、`stabstr_section`:在启用调试时用于存储调试信息。
- `symtab_section`、`strtab_section`:包含导出符号(目前仅用于调试)。
##### 8.7 代码生成
###### 8.7.1 引言
TCC 代码生成器直接生成链接的二进制代码,仅需一次遍历。这在当今较为少见(例如 gcc 生成文本汇编),但速度很快且复杂度低。
TCC 代码生成器基于寄存器。优化仅在表达式级别进行,不保留表达式的中间表示,除非存储在值栈中的当前值。
在 x86 上,使用三个临时寄存器。当需要更多寄存器时,一个寄存器会被溢出到栈上的新临时变量。
###### 8.7.2 值栈
解析表达式时,其值被推入值栈(vstack)。栈顶为 `vtop`。每个值栈条目是 `SValue` 结构。
- `SValue.t` 是类型。
- `SValue.r` 指示值在生成代码中的存储方式,通常是 CPU 寄存器索引(REG_xxx 常量),但定义了附加值和标志:
```c
#define VT_CONST 0x00f0
#define VT_LLOCAL 0x00f1
#define VT_LOCAL 0x00f2
#define VT_CMP 0x00f3
#define VT_JMP 0x00f4
#define VT_JMPI 0x00f5
#define VT_LVAL 0x0100
#define VT_SYM 0x0200
#define VT_MUSTCAST 0x0400
#define VT_MUSTBOUND 0x0800
#define VT_BOUNDED 0x8000
#define VT_LVAL_BYTE 0x1000
#define VT_LVAL_SHORT 0x2000
#define VT_LVAL_UNSIGNED 0x4000
#define VT_LVAL_TYPE (VT_LVAL_BYTE | VT_LVAL_SHORT | VT_LVAL_UNSIGNED)
```
- `VT_CONST`:表示值是常量,存储在 `SValue.c` 中,具体取决于类型。
- `VT_LOCAL`:表示栈中偏移量为 `SValue.c.i` 的局部变量指针。
- `VT_CMP`:表示值存储在 CPU 标志中(测试结果)。值为 0 或 1,实际 CPU 标志存储在 `SValue.c.i` 中。若生成破坏 CPU 标志的代码,必须将此值放入普通寄存器。
- `VT_JMP`、`VT_JMPI`:表示值是条件跳转的结果。对于 `VT_JMP`,跳转发生时为 1,否则为 0;`VT_JMPI` 相反。用于编译 || 和 && 逻辑运算符。若生成代码,此值必须放入普通寄存器,否则跳转发生时代码不会执行。
- `VT_LVAL`:表示值是赋值左侧值(lvalue),存储的是指向所需值的指针。理解 `VT_LVAL` 对理解 TCC 工作原理非常重要。
- `VT_LVAL_BYTE`、`VT_LVAL_SHORT`、`VT_LVAL_UNSIGNED`:若 lvalue 是整数类型,这些标志提供其真实类型。仅类型不足以应对类型转换优化。
- `VT_LLOCAL`:栈上保存的 lvalue,必须与 `VT_LVAL` 一起设置。可能因寄存器中的 `VT_LVAL` 需要保存到栈,或因特定架构的调用约定而产生。
- `VT_MUSTCAST`:表示若使用值,必须执行到值类型的转换(延迟转换)。
- `VT_SYM`:表示必须将 `SValue.sym` 添加到常量。
- `VT_MUSTBOUND`、`VT_BOUNDED`:仅用于可选边界检查。
###### 8.7.3 操作值栈
- `vsetc()` 和 `vset()` 将新值推入值栈。若之前的 `vtop` 存储在不安全位置(例如 CPU 标志),则生成代码将其放入安全存储。
- `vpop()` 弹出 `vtop`,某些情况下会生成清理代码(例如 x86 上使用的栈式浮点寄存器)。
- `gv(rc)` 生成代码将 `vtop` 评估到寄存器中,`rc` 选择寄存器类。`gv()` 是代码生成器最重要的函数。
- `gv2()` 与 `gv()` 相同,但用于栈顶两个条目。
###### 8.7.4 依赖于 CPU 的代码生成
参见 i386-gen.c 文件以获取示例。
- `load()`:生成将栈值加载到寄存器的代码。
- `store()`:生成将寄存器存储到栈值 lvalue 的代码。
- `gfunc_start()`、`gfunc_param()`、`gfunc_call()`:生成函数调用。
- `gfunc_prolog()`、`gfunc_epilog()`:生成函数序言/结尾。
- `gen_opi(op)`:为栈顶两个整数类型条目生成二进制整数操作 op,结果放入栈中。
- `gen_opf(op)`:与 `gen_opi()` 类似,用于浮点操作,栈顶两个条目保证是相同类型的浮点值。
- `gen_cvt_itof()`:整数到浮点转换。
- `gen_cvt_ftoi()`:浮点到整数转换。
- `gen_cvt_ftof()`:不同大小浮点到浮点的转换。
- `gen_bounded_ptr_add()`、`gen_bounded_ptr_deref()`:仅用于边界检查。
##### 8.8 已完成的优化
- 所有操作进行常量传播。
- 乘法和除法在适当情况下优化为移位。
- 比较运算符通过维护处理器标志的特殊缓存进行优化。
- `&&`、`||` 和 `!` 通过维护特殊“跳转目标”值进行优化。
- 目前不执行其他跳转优化,因为这需要以更抽象的方式存储代码。
---
#### 概念索引
(索引内容翻译为中文,格式保持不变)
跳转到: _
A B C D E F G I J L M O P Q R S T U V W
**索引条目** | **章节**
--- | ---
_ |
`__asm__` | C 语言支持
A |
`align` 指令 | 汇编器
`aligned` 属性 | C 语言支持
`ascii` 指令 | 汇编器
`asciz` 指令 | 汇编器
汇编器 | 汇编器
汇编器指令 | 汇编器
内联汇编 | C 语言支持
B |
边界检查 | 内存和边界检查
`bss` 指令 | 汇编器
`byte` 指令 | 汇编器
C |
缓存处理器标志 | 开发者指南
`cdecl` 属性 | C 语言支持
代码生成 | 开发者指南
比较运算符 | 开发者指南
常量传播 | 开发者指南
依赖于 CPU | 开发者指南
D |
`data` 指令 | 汇编器
指令,汇编器 | 汇编器
`dllexport` 属性 | C 语言支持
E |
ELF | 链接器
F |
`FILE`,链接器命令 | 链接器
`fill` 指令 | 汇编器
标志,缓存 | 开发者指南
G |
gas | C 语言支持
`global` 指令 | 汇编器
`globl` 指令 | 汇编器
`GROUP`,链接器命令 | 链接器
I |
内联汇编 | C 语言支持
`int` 指令 | 汇编器
J |
跳转优化 | 开发者指南
L |
链接器 | 链接器
链接器脚本 | 链接器
`long` 指令 | 汇编器
M |
内存检查 | 内存和边界检查
O |
优化 | 开发者指南
`org` 指令 | 汇编器
`OUTPUT_FORMAT`,链接器命令 | 链接器
P |
`packed` 属性 | C 语言支持
PE-i386 | 链接器
`previous` 指令 | 汇编器
Q |
`quad` 指令 | 汇编器
R |
`regparm` 属性 | C 语言支持
S |
脚本,链接器 | 链接器
`section` 属性 | C 语言支持
`section` 指令 | 汇编器
`short` 指令 | 汇编器
`skip` 指令 | 汇编器
`space` 指令 | 汇编器
`stdcall` 属性 | C 语言支持
强度降低 | 开发者指南
`string` 指令 | 汇编器
T |
`TARGET`,链接器命令 | 链接器
`text` 指令 | 汇编器
U |
`unused` 属性 | C 语言支持
V |
值栈 | 开发者指南
值栈,引言 | 开发者指南
W |
`word` 指令 | 汇编器
跳转到: _
A B C D E F G I J L M O P Q R S T U V W
---