Tinyc编译器(翻译)

### 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  

---

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值