深入解析DoctorWkt/acwj项目中的全局变量初始化实现
acwj A Compiler Writing Journey 项目地址: https://gitcode.com/gh_mirrors/ac/acwj
前言
在编译器开发领域,变量初始化是一个基础但至关重要的功能。本文将深入探讨DoctorWkt/acwj项目中全局变量初始化的实现细节,帮助读者理解编译器如何处理不同类型的全局变量初始化。
全局变量初始化概述
在C语言中,全局变量可以有多种初始化形式,包括:
int x = 2; // 标量初始化
char *str = "Hello world"; // 字符串指针初始化
int a[10]; // 数组声明
char b[] = {'q','w','e','r'}; // 数组初始化
编译器需要能够正确解析这些语法,并生成相应的汇编代码来初始化这些全局变量。
标量变量初始化实现
字面量解析
编译器首先需要能够解析不同类型的字面量值。项目中实现了一个关键函数parse_literal()
:
int parse_literal(int type) {
// 处理字符串字面量
if ((type == pointer_to(P_CHAR)) && (Token.token == T_STRLIT))
return(genglobstr(Text));
// 处理整数字面量
if (Token.token == T_INTLIT) {
switch(type) {
case P_CHAR:
if (Token.intvalue < 0 || Token.intvalue > 255)
fatal("字符类型字面值超出范围");
case P_INT:
case P_LONG: break;
default: fatal("类型不匹配:整数字面值与变量");
}
} else
fatal("期望整数字面值");
return(Token.intvalue);
}
这个函数会根据变量类型验证字面量的有效性,并返回相应的值或字符串标签。
符号表结构变更
为了支持初始化功能,项目的符号表结构进行了重要修改:
struct symtable {
...
int size; // 符号总大小(字节)
int nelems; // 函数:参数数量;数组:元素数量
int *initlist; // 初始化值列表
...
};
这些字段使得编译器能够跟踪变量的尺寸、元素数量以及初始值。
数组初始化实现
数组初始化比标量更复杂,需要处理三种情况:
- 固定大小数组(可能部分初始化)
- 未指定大小的数组(根据初始化列表确定大小)
- 混合情况
数组声明解析
数组声明的解析逻辑如下:
static struct symtable *array_declaration(...) {
int nelems = -1; // 初始设为-1表示未指定大小
// 解析数组大小(如果有)
if (Token.token == T_INTLIT) {
if (Token.intvalue <= 0)
fatald("非法数组大小", Token.intvalue);
nelems = Token.intvalue;
scan(&Token);
}
// 处理初始化列表
if (Token.token == T_ASSIGN) {
match(T_LBRACE, "{");
// 动态调整初始化列表大小
if (nelems != -1)
maxelems = nelems;
else
maxelems = TABLE_INCREMENT;
initlist = (int *)malloc(maxelems * sizeof(int));
// 解析初始化值
while (1) {
if (nelems != -1 && i == maxelems)
fatal("初始化列表值过多");
initlist[i++] = parse_literal(type);
// 动态扩展列表
if (nelems == -1 && i == maxelems) {
maxelems += TABLE_INCREMENT;
initlist = (int *)realloc(initlist, maxelems * sizeof(int));
}
if (Token.token == T_RBRACE) break;
comma();
}
// 处理未初始化的元素
for (j=i; j < sym->nelems; j++) initlist[j]=0;
sym->initlist = initlist;
}
// 设置数组最终大小
sym->nelems = nelems;
sym->size = sym->nelems * typesize(type, ctype);
}
汇编代码生成
初始化后的全局变量需要生成相应的汇编代码。关键函数cgglobsym()
负责此功能:
void cgglobsym(struct symtable *node) {
// 确定元素大小和类型
if (node->stype == S_ARRAY) {
size = typesize(value_at(node->type), node->ctype);
type = value_at(node->type);
} else {
size = node->size;
type = node->type;
}
// 为每个元素生成初始化代码
for (i=0; i < node->nelems; i++) {
initvalue = (node->initlist != NULL) ? node->initlist[i] : 0;
switch (size) {
case 1: fprintf(Outfile, "\t.byte\t%d\n", initvalue); break;
case 4: fprintf(Outfile, "\t.long\t%d\n", initvalue); break;
case 8:
if (type == pointer_to(P_CHAR))
fprintf(Outfile, "\t.quad\tL%d\n", initvalue);
else
fprintf(Outfile, "\t.quad\t%d\n", initvalue);
break;
default: // 处理大尺寸类型
}
}
}
实际应用示例
以下是一些初始化示例及其生成的汇编代码:
- 标量初始化:
int x = 5;
生成:
.globl x
x:
.long 5
- 字符串指针初始化:
char *y = "Hello";
生成:
L1:
.byte 72
.byte 101
.byte 108
.byte 108
.byte 111
.byte 0
.globl y
y:
.quad L1
- 数组初始化:
int arr[4] = {1, 4, 17};
生成:
.globl arr
arr:
.long 1
.long 4
.long 17
.long 0
总结与展望
本文详细分析了DoctorWkt/acwj项目中全局变量初始化的实现机制。通过修改符号表结构、完善字面量解析逻辑以及增强数组处理能力,编译器现在能够正确处理各种全局变量初始化场景。
这种实现不仅考虑了语法正确性,还关注了内存布局和汇编代码生成的效率。动态调整初始化列表大小的设计特别值得注意,它优雅地处理了未指定大小数组的初始化问题。
在后续开发中,可以进一步扩展此功能以支持更复杂的初始化场景,如结构体和联合体的初始化,以及静态局部变量的初始化处理。这些扩展将基于本文介绍的基础架构,进一步丰富编译器的功能集。
acwj A Compiler Writing Journey 项目地址: https://gitcode.com/gh_mirrors/ac/acwj
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考