CSP-S 2023 结构体

GitHub跳转原题关系图返回列表

纯模拟题,按题目规则模拟结构体类型的定义、元素的内存分配与访问查找。

OJ: roj

题目 ID: 3678

难度:提高

标签:模拟

创建: 2026-10-10 09:26

更新: 2026-10-10 09:26

形式化题目

实现一个包含四种操作的内存分配模拟器:

  1. 定义结构体:给定类型名与成员列表,计算并输出该结构体的大小与内存对齐要求。
  2. 定义元素:给定类型与元素名,分配内存,并根据前一个元素的占用情况以及当前类型的对齐要求,输出起始内存地址。
  3. 访问成员:给定以 . 分隔的多级成员名称,计算并输出其确切的起始内存地址。
  4. 访问地址:给定一个内存地址,逆向推导出占据该地址的最底层基本类型元素的完整名称;若该地址落在填充的内存“空洞”中或未分配内存,输出 ERR。

数据范围:n≤100,k≤100n \le 100, k \le 100,任意元素的最高内存地址不超过 101810^{18}。

正解

思路

本题是一道纯粹的模拟题,我们只需严格按照题目给出的形式化规则维护类型和变量的信息即可:

  1. 类型的表示:将基本类型也看作特殊的“结构体”(即不包含任何成员的结构体),这样就统一了基础类型和自定义类型的表示。每种类型包含其自身的大小(size)、对齐要求(align)、成员列表(成员包括名称、类型、偏移量)。
  2. 结构体定义(操作 1): 维护一个偏移量指针 offset 初始为 00。遍历新结构体的所有成员,根据该成员类型的对齐要求更新 offset = align_up(offset, 成员的 align),此时的 offset 即为该成员在结构体内的相对地址。随后 offset 增加该成员的大小。 结构体的总对齐要求为所有成员中对齐要求的最大值。 最终结构体的总大小为 align_up(offset, 总 align)。
  3. 变量定义(操作 2): 顺序记录分配的变量。如果存在前置变量,则新变量的起始可用地址为前置变量的 起始地址 + 它的类型大小。将该可用地址通过新变量类型的对齐要求向上取整,即得到其实际的起始地址。
  4. 正向访问(操作 3): 将查询字符串按 . 切分。第一级为全局变量名称,从而得到其起始地址和当前类型。随后逐级向下,查表加上成员在当前类型中的相对偏移量即可。
  5. 逆向查询(操作 4): 遍历已定义的全局变量列表,判断查询地址是否落在某变量的 [起始地址, 起始地址 + 类型大小) 范围内。 如果不在任何变量内,输出 ERR;否则,计算相对于该变量起始地址的偏移量,并递归在类型结构内向下查找。每次找到最后一个相对偏移量小于等于当前查询偏移量的成员:
    • 若查询偏移量落在了该成员的有效大小内(偏移量 < 成员起始相对地址 + 成员大小),则说明查询命中该成员,并继续深入其内部。
    • 若查询偏移量落在了成员大小以外,说明落入了对齐补齐的空洞中,直接输出 ERR。 重复此过程,直到深入到基本类型为止,即可得到完整名称。

由于操作数 n≤100,k≤100n \le 100, k \le 100,利用 map 维护映射关系以及直接遍历的方法是足够高效的,时空复杂度均有保证。

代码

python
import sys

def main():
    input = sys.stdin.read
    data = input().split()
    if not data:
        return
    
    n = int(data[0])
    idx = 1
    
    types = {
        "byte": {"size": 1, "align": 1, "members": [], "name_to_index": {}},
        "short": {"size": 2, "align": 2, "members": [], "name_to_index": {}},
        "int": {"size": 4, "align": 4, "members": [], "name_to_index": {}},
        "long": {"size": 8, "align": 8, "members": [], "name_to_index": {}},
    }
    
    vars_list = []
    var_name_to_index = {}
    
    out = []
    
    for _ in range(n):
        op = int(data[idx])
        idx += 1
        
        if op == 1:
            name = data[idx]
            k = int(data[idx+1])
            idx += 2
            
            align = 0
            current_offset = 0
            members = []
            name_to_index = {}
            
            for i in range(k):
                type_name = data[idx]
                member_name = data[idx+1]
                idx += 2
                
                member_type = types[type_name]
                m_align = member_type["align"]
                m_size = member_type["size"]
                
                if align < m_align:
                    align = m_align
                
                current_offset = (current_offset + m_align - 1) // m_align * m_align
                members.append({
                    "type": type_name,
                    "name": member_name,
                    "offset": current_offset
                })
                name_to_index[member_name] = i
                current_offset += m_size
                
            size = (current_offset + align - 1) // align * align
            types[name] = {
                "size": size,
                "align": align,
                "members": members,
                "name_to_index": name_to_index
            }
            out.append(f"{size} {align}")
            
        elif op == 2:
            type_name = data[idx]
            var_name = data[idx+1]
            idx += 2
            
            t = types[type_name]
            t_align = t["align"]
            
            start_addr = 0
            if vars_list:
                last_var = vars_list[-1]
                last_type = types[last_var["type"]]
                next_avail = last_var["addr"] + last_type["size"]
                start_addr = (next_avail + t_align - 1) // t_align * t_align
                
            vars_list.append({
                "type": type_name,
                "name": var_name,
                "addr": start_addr
            })
            var_name_to_index[var_name] = len(vars_list) - 1
            out.append(str(start_addr))
            
        elif op == 3:
            path = data[idx]
            idx += 1
            
            parts = path.split('.')
            var_idx = var_name_to_index[parts[0]]
            ans = vars_list[var_idx]["addr"]
            curr_type = vars_list[var_idx]["type"]
            
            for i in range(1, len(parts)):
                t = types[curr_type]
                member_idx = t["name_to_index"][parts[i]]
                m = t["members"][member_idx]
                ans += m["offset"]
                curr_type = m["type"]
                
            out.append(str(ans))
            
        elif op == 4:
            addr = int(data[idx])
            idx += 1
            
            found_var_idx = -1
            for i, v in enumerate(vars_list):
                if v["addr"] <= addr < v["addr"] + types[v["type"]]["size"]:
                    found_var_idx = i
                    break
            
            if found_var_idx == -1:
                out.append("ERR")
                continue
                
            res = vars_list[found_var_idx]["name"]
            curr_type = vars_list[found_var_idx]["type"]
            rem_addr = addr - vars_list[found_var_idx]["addr"]
            
            ok = True
            while types[curr_type]["members"]:
                t = types[curr_type]
                found_member_idx = -1
                for i in range(len(t["members"]) - 1, -1, -1):
                    if rem_addr >= t["members"][i]["offset"]:
                        found_member_idx = i
                        break
                
                if found_member_idx == -1:
                    ok = False
                    break
                
                m = t["members"][found_member_idx]
                m_size = types[m["type"]]["size"]
                
                if rem_addr < m["offset"] + m_size:
                    res += "." + m["name"]
                    rem_addr -= m["offset"]
                    curr_type = m["type"]
                else:
                    ok = False
                    break
                    
            if ok:
                out.append(res)
            else:
                out.append("ERR")

    print('\n'.join(out))

if __name__ == '__main__':
    main()
cpp
#include <iostream>
#include <string>
#include <vector>
#include <map>

using namespace std;

struct Member {
    string type;
    string name;
    long long offset;
};

struct Type {
    long long size;
    long long align;
    vector<Member> members;
    map<string, int> name_to_index;
};

struct Variable {
    string type;
    string name;
    long long addr;
};

map<string, Type> types;
vector<Variable> vars;
map<string, int> var_name_to_index;

vector<string> split(const string& s, char delim) {
    vector<string> res;
    string item;
    for (char c : s) {
        if (c == delim) {
            res.push_back(item);
            item = "";
        } else {
            item += c;
        }
    }
    res.push_back(item);
    return res;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    types["byte"] = {1, 1, {}, {}};
    types["short"] = {2, 2, {}, {}};
    types["int"] = {4, 4, {}, {}};
    types["long"] = {8, 8, {}, {}};

    int n;
    if (!(cin >> n)) return 0;

    while (n--) {
        int op;
        cin >> op;
        if (op == 1) {
            string name;
            int k;
            cin >> name >> k;
            Type t;
            t.align = 0;
            long long current_offset = 0;
            for (int i = 0; i < k; ++i) {
                string type_name, member_name;
                cin >> type_name >> member_name;
                Type& member_type = types[type_name];
                t.align = max(t.align, member_type.align);
                current_offset = (current_offset + member_type.align - 1) / member_type.align * member_type.align;
                t.members.push_back({type_name, member_name, current_offset});
                t.name_to_index[member_name] = i;
                current_offset += member_type.size;
            }
            t.size = (current_offset + t.align - 1) / t.align * t.align;
            types[name] = t;
            cout << t.size << " " << t.align << "\n";
        } else if (op == 2) {
            string type_name, var_name;
            cin >> type_name >> var_name;
            Type& t = types[type_name];
            long long start_addr = 0;
            if (!vars.empty()) {
                Variable& last_var = vars.back();
                Type& last_type = types[last_var.type];
                long long next_avail = last_var.addr + last_type.size;
                start_addr = (next_avail + t.align - 1) / t.align * t.align;
            }
            vars.push_back({type_name, var_name, start_addr});
            var_name_to_index[var_name] = (int)vars.size() - 1;
            cout << start_addr << "\n";
        } else if (op == 3) {
            string path;
            cin >> path;
            vector<string> parts = split(path, '.');
            int var_idx = var_name_to_index[parts[0]];
            long long ans = vars[var_idx].addr;
            string curr_type = vars[var_idx].type;
            for (size_t i = 1; i < parts.size(); ++i) {
                Type& t = types[curr_type];
                int member_idx = t.name_to_index[parts[i]];
                ans += t.members[member_idx].offset;
                curr_type = t.members[member_idx].type;
            }
            cout << ans << "\n";
        } else if (op == 4) {
            long long addr;
            cin >> addr;
            int found_var_idx = -1;
            for (int i = 0; i < (int)vars.size(); ++i) {
                if (addr >= vars[i].addr && addr < vars[i].addr + types[vars[i].type].size) {
                    found_var_idx = i;
                    break;
                }
            }
            if (found_var_idx == -1) {
                cout << "ERR\n";
                continue;
            }
            string res = vars[found_var_idx].name;
            string curr_type = vars[found_var_idx].type;
            long long rem_addr = addr - vars[found_var_idx].addr;
            bool ok = true;
            while (types[curr_type].members.size() > 0) {
                Type& t = types[curr_type];
                int found_member_idx = -1;
                for (int i = (int)t.members.size() - 1; i >= 0; --i) {
                    if (rem_addr >= t.members[i].offset) {
                        found_member_idx = i;
                        break;
                    }
                }
                if (found_member_idx == -1) {
                    ok = false;
                    break;
                }
                Member& m = t.members[found_member_idx];
                if (rem_addr < m.offset + types[m.type].size) {
                    res += "." + m.name;
                    rem_addr -= m.offset;
                    curr_type = m.type;
                } else {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                cout << res << "\n";
            } else {
                cout << "ERR\n";
            }
        }
    }
    return 0;
}

复杂度

  • 时间复杂度:
    • 操作 1 和 2 主要为查找与算术计算,单次至多遍历 kk 个成员,为 O(k)O(k)。
    • 操作 3 和 4 最多深入类型嵌套深度次(最大为 nn),每层使用哈希表或二分/遍历查找,复杂度 O(n)O(n)。 总体时间复杂度极小,不超过 O(n2)O(n^2),能够在数毫秒内完成处理。
  • 空间复杂度:需要存储不超过 nn 个结构体,每个结构体包含至多 kk 个成员,存储这些信息需要 O(nk)O(nk) 的空间;同时还有 O(n)O(n) 的变量存储。总空间复杂度为 O(nk)O(nk)。

总结

  • 理解内存对齐机制:对齐大小由成员的最大对齐要求决定,而偏移量及最终大小必须依次向上取整至对齐要求的整数倍。
  • 正反向映射的统一:用统一的数据结构去管理基本类型与复合类型,能够极大减少分类讨论带来的代码膨胀和潜在 bug。
  • 内存空洞的判断:在逆向查询时,必须在每深入一层之前,严谨判定当前偏移量是否位于该成员大小所覆盖的区间内,否则即为无效地址。