第145集:结构体访问的中间代码生成

核心知识点讲解

结构体的存储方式

在内存中,结构体(Struct)通常采用连续存储的方式,即结构体的各个字段在内存中是连续排列的。结构体的大小等于所有字段大小的总和(可能会有内存对齐的情况)。

例如,对于以下结构体:

struct Person {
    int age;
    char name[20];
    float height;
};

假设 int 占4字节,char 占1字节,float 占4字节,并且没有内存对齐的情况下,该结构体的大小为 4 + 20 + 4 = 28 字节。

结构体字段的偏移计算

要访问结构体的字段,首先需要计算该字段在结构体中的偏移量。偏移量是指字段相对于结构体起始地址的字节数。

例如,对于上面的 Person 结构体:

  • age 字段的偏移量为 0
  • name 字段的偏移量为 4
  • height 字段的偏移量为 24(4 + 20)

结构体访问的类型

结构体访问通常可以分为以下几种类型:

  1. 直接访问:通过结构体变量直接访问字段,如 person.age

  2. 指针访问:通过结构体指针访问字段,如 ptr->age

  3. 嵌套访问:访问嵌套结构体的字段,如 person.address.street

结构体访问的中间代码生成

生成结构体访问的中间代码需要以下步骤:

  1. 计算字段的偏移量:根据结构体的定义,计算字段在结构体中的偏移量。

  2. 计算字段的地址:根据结构体的基地址和字段的偏移量,计算字段的地址。

  3. 生成访问指令:根据访问类型(读取或写入),生成相应的访问指令。

直接访问的中间代码生成

对于直接访问 person.age,生成的中间代码如下:

  1. 计算地址:t1 = person + offset(age)
  2. 读取值:t2 = *t1(如果是读取操作)
    或写入值:*t1 = x(如果是写入操作)

指针访问的中间代码生成

对于指针访问 ptr->age,生成的中间代码如下:

  1. 计算地址:t1 = *ptr + offset(age)
  2. 读取值:t2 = *t1(如果是读取操作)
    或写入值:*t1 = x(如果是写入操作)

嵌套访问的中间代码生成

对于嵌套访问 person.address.street,生成的中间代码如下:

  1. 计算 address 字段的地址:t1 = person + offset(address)
  2. 计算 street 字段的地址:t2 = t1 + offset(street)
  3. 读取值: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

总结

结构体访问的中间代码生成是编译器前端的重要组成部分,它涉及到结构体字段的偏移计算和访问指令的生成。对于直接访问、指针访问和嵌套访问,它们的中间代码生成方式有所不同,但核心都是计算字段的地址然后进行访问。

在生成结构体访问的中间代码时,需要注意以下几点:

  1. 字段偏移量:需要准确计算字段在结构体中的偏移量,这通常需要考虑内存对齐的情况。

  2. 内存对齐:为了提高内存访问效率,编译器通常会对结构体进行内存对齐,这会影响字段的偏移量计算。

  3. 指针访问:对于指针访问,需要先获取指针指向的结构体的地址,然后再计算字段的地址。

  4. 嵌套访问:对于嵌套结构体,需要逐层计算字段的地址,直到到达目标字段。

  5. 结构体数组:对于结构体数组,需要先计算数组元素的地址,然后再计算字段的地址。

通过正确生成结构体访问的中间代码,可以确保编译器能够正确处理结构体操作,为后续的代码优化和目标代码生成做准备。

« 上一篇 数组访问的中间代码生成 下一篇 » 赋值语句的中间代码生成