第145集:结构体访问的中间代码生成
核心知识点讲解
结构体的存储方式
在内存中,结构体(Struct)通常采用连续存储的方式,即结构体的各个字段在内存中是连续排列的。结构体的大小等于所有字段大小的总和(可能会有内存对齐的情况)。
例如,对于以下结构体:
struct Person {
int age;
char name[20];
float height;
};假设 int 占4字节,char 占1字节,float 占4字节,并且没有内存对齐的情况下,该结构体的大小为 4 + 20 + 4 = 28 字节。
结构体字段的偏移计算
要访问结构体的字段,首先需要计算该字段在结构体中的偏移量。偏移量是指字段相对于结构体起始地址的字节数。
例如,对于上面的 Person 结构体:
age字段的偏移量为 0name字段的偏移量为 4height字段的偏移量为 24(4 + 20)
结构体访问的类型
结构体访问通常可以分为以下几种类型:
直接访问:通过结构体变量直接访问字段,如
person.age。指针访问:通过结构体指针访问字段,如
ptr->age。嵌套访问:访问嵌套结构体的字段,如
person.address.street。
结构体访问的中间代码生成
生成结构体访问的中间代码需要以下步骤:
计算字段的偏移量:根据结构体的定义,计算字段在结构体中的偏移量。
计算字段的地址:根据结构体的基地址和字段的偏移量,计算字段的地址。
生成访问指令:根据访问类型(读取或写入),生成相应的访问指令。
直接访问的中间代码生成
对于直接访问 person.age,生成的中间代码如下:
- 计算地址:
t1 = person + offset(age) - 读取值:
t2 = *t1(如果是读取操作)
或写入值:*t1 = x(如果是写入操作)
指针访问的中间代码生成
对于指针访问 ptr->age,生成的中间代码如下:
- 计算地址:
t1 = *ptr + offset(age) - 读取值:
t2 = *t1(如果是读取操作)
或写入值:*t1 = x(如果是写入操作)
嵌套访问的中间代码生成
对于嵌套访问 person.address.street,生成的中间代码如下:
- 计算
address字段的地址:t1 = person + offset(address) - 计算
street字段的地址:t2 = t1 + offset(street) - 读取值:
t3 = *t2(如果是读取操作)
或写入值:*t2 = x(如果是写入操作)
实用案例分析
案例1:结构体的直接访问(读取)
示例代码
struct Person {
int age;
char name[20];
float height;
};
struct Person person;
int x = person.age;偏移计算
age 字段的偏移量为 0,所以 person.age 的地址为 &person + 0。
生成的三地址码
t1 = person + 0 // 计算 age 字段的地址
t2 = *t1 // 读取值
x = t2 // 赋值给变量 x案例2:结构体的直接访问(写入)
示例代码
person.age = 25;偏移计算
同样,age 字段的偏移量为 0。
生成的三地址码
t1 = person + 0 // 计算 age 字段的地址
*t1 = 25 // 写入值案例3:结构体的指针访问(读取)
示例代码
struct Person *ptr = &person;
int x = ptr->age;偏移计算
首先需要获取指针指向的结构体的地址,然后加上 age 字段的偏移量。
生成的三地址码
t1 = *ptr // 获取结构体的地址
t2 = t1 + 0 // 计算 age 字段的地址
t3 = *t2 // 读取值
x = t3 // 赋值给变量 x案例4:结构体的指针访问(写入)
示例代码
ptr->age = 30;生成的三地址码
t1 = *ptr // 获取结构体的地址
t2 = t1 + 0 // 计算 age 字段的地址
*t2 = 30 // 写入值案例5:嵌套结构体的访问(读取)
示例代码
struct Address {
char street[50];
int zip;
};
struct Person {
int age;
struct Address address;
float height;
};
struct Person person;
int x = person.address.zip;偏移计算
address字段的偏移量为 4(age字段的大小)zip字段在Address结构体中的偏移量为 50(street字段的大小)- 所以
person.address.zip的总偏移量为4 + 50 = 54
生成的三地址码
t1 = person + 4 // 计算 address 字段的地址
t2 = t1 + 50 // 计算 zip 字段的地址
t3 = *t2 // 读取值
x = t3 // 赋值给变量 x案例6:结构体数组的访问
示例代码
struct Person people[10];
int x = people[2].age;偏移计算
- 首先计算
people[2]的地址:&people + 2 * sizeof(struct Person) - 然后计算
age字段的地址:加上偏移量 0
生成的三地址码
t1 = 2 * 28 // 计算 people[2] 的偏移量(假设 struct Person 大小为 28 字节)
t2 = people + t1 // 计算 people[2] 的地址
t3 = t2 + 0 // 计算 age 字段的地址
t4 = *t3 // 读取值
x = t4 // 赋值给变量 x案例7:结构体作为函数参数
示例代码
void print_person(struct Person p) {
printf("Age: %d\n", p.age);
}
int main() {
struct Person person;
person.age = 25;
print_person(person);
return 0;
}生成的三地址码
// print_person 函数的中间代码
print_person:
// 函数序言
// 计算 p.age 的地址并读取值
t1 = p + 0
t2 = *t1
// 调用 printf 函数
param "Age: %d\n"
param t2
call printf, 2
// 函数尾声
return
// main 函数的中间代码
main:
// 函数序言
// 给 person.age 赋值
t1 = person + 0
*t1 = 25
// 调用 print_person 函数
param person
call print_person, 1
// 返回
return 0代码实现
下面是一个简单的结构体访问中间代码生成器的实现,使用 Python 语言:
class StructCodeGenerator:
def __init__(self):
self.temp_count = 0
self.instructions = []
# 结构体字段偏移量信息(模拟符号表)
self.struct_layouts = {
'Person': {
'age': 0,
'name': 4,
'height': 24
},
'Address': {
'street': 0,
'zip': 50
},
'NestedPerson': {
'age': 0,
'address': 4,
'height': 54 # 4 + 50 (Address 大小)
}
}
def new_temp(self):
"""生成新的临时变量名"""
self.temp_count += 1
return f"t{self.temp_count}"
def get_field_offset(self, struct_name, field_name):
"""获取结构体字段的偏移量"""
if struct_name in self.struct_layouts:
if field_name in self.struct_layouts[struct_name]:
return self.struct_layouts[struct_name][field_name]
raise ValueError(f"Field {field_name} not found in struct {struct_name}")
def generate_struct_access(self, struct_expr, struct_name, field_name):
"""生成结构体字段访问的中间代码
Args:
struct_expr: 结构体表达式(变量名或指针表达式)
struct_name: 结构体类型名
field_name: 字段名
Returns:
存储字段地址的临时变量名
"""
# 获取字段偏移量
offset = self.get_field_offset(struct_name, field_name)
# 检查是否是指针访问
if struct_expr.startswith('*'):
# 指针访问,如 *ptr
base_temp = self.new_temp()
self.instructions.append(f"{base_temp} = {struct_expr}")
else:
# 直接访问,如 person
base_temp = struct_expr
# 计算字段地址
addr_temp = self.new_temp()
self.instructions.append(f"{addr_temp} = {base_temp} + {offset}")
return addr_temp
def generate_struct_read(self, struct_expr, struct_name, field_name, result_var):
"""生成结构体字段读取的中间代码"""
addr_temp = self.generate_struct_access(struct_expr, struct_name, field_name)
# 生成读取指令
self.instructions.append(f"{result_var} = *{addr_temp}")
def generate_struct_write(self, struct_expr, struct_name, field_name, value_var):
"""生成结构体字段写入的中间代码"""
addr_temp = self.generate_struct_access(struct_expr, struct_name, field_name)
# 生成写入指令
self.instructions.append(f"*{addr_temp} = {value_var}")
def generate_nested_struct_access(self, struct_expr, struct_path):
"""生成嵌套结构体字段访问的中间代码
Args:
struct_expr: 结构体表达式
struct_path: 结构体路径,如 ['Person', 'address', 'zip']
Returns:
存储字段地址的临时变量名
"""
if len(struct_path) < 2:
raise ValueError("Struct path must have at least two elements: [struct_name, field1, field2, ...]")
struct_name = struct_path[0]
current_expr = struct_expr
# 处理前 N-1 个字段(嵌套结构体)
for i in range(1, len(struct_path) - 1):
field_name = struct_path[i]
current_expr = self.generate_struct_access(current_expr, struct_name, field_name)
# 更新结构体类型名(假设知道下一级的结构体类型)
# 在实际编译器中,这些信息会从符号表中获取
if field_name == 'address':
struct_name = 'Address'
# 处理最后一个字段
field_name = struct_path[-1]
return self.generate_struct_access(current_expr, struct_name, field_name)
def get_instructions(self):
"""获取生成的中间代码指令"""
return self.instructions
# 测试代码
generator = StructCodeGenerator()
# 测试直接访问
print("测试直接访问:")
generator.generate_struct_read("person", "Person", "age", "x")
for instr in generator.get_instructions():
print(instr)
print()
# 测试指针访问
generator2 = StructCodeGenerator()
print("测试指针访问:")
generator2.generate_struct_read("*ptr", "Person", "age", "y")
for instr in generator2.get_instructions():
print(instr)
print()
# 测试嵌套访问
generator3 = StructCodeGenerator()
print("测试嵌套访问:")
addr_temp = generator3.generate_nested_struct_access("person", ["NestedPerson", "address", "zip"])
generator3.instructions.append(f"z = *{addr_temp}")
for instr in generator3.get_instructions():
print(instr)运行结果
测试直接访问:
t1 = person + 0
x = *t1
测试指针访问:
t1 = *ptr
t2 = t1 + 0
y = *t2
测试嵌套访问:
t1 = person + 4
t2 = t1 + 50
z = *t2总结
结构体访问的中间代码生成是编译器前端的重要组成部分,它涉及到结构体字段的偏移计算和访问指令的生成。对于直接访问、指针访问和嵌套访问,它们的中间代码生成方式有所不同,但核心都是计算字段的地址然后进行访问。
在生成结构体访问的中间代码时,需要注意以下几点:
字段偏移量:需要准确计算字段在结构体中的偏移量,这通常需要考虑内存对齐的情况。
内存对齐:为了提高内存访问效率,编译器通常会对结构体进行内存对齐,这会影响字段的偏移量计算。
指针访问:对于指针访问,需要先获取指针指向的结构体的地址,然后再计算字段的地址。
嵌套访问:对于嵌套结构体,需要逐层计算字段的地址,直到到达目标字段。
结构体数组:对于结构体数组,需要先计算数组元素的地址,然后再计算字段的地址。
通过正确生成结构体访问的中间代码,可以确保编译器能够正确处理结构体操作,为后续的代码优化和目标代码生成做准备。