P9754 CSP-S 2023 "구조체" 문제 해설

1. 문제 핵심

프로그램이 실행되는 동안 새로운 구조체 타입을 동적으로 정의하고, 그 타입으로 전역 변수를 만든 뒤, 버 경로를 오프셋으로 변환하거나 반대로 주소를 멤버 경로로 환원해야 한다. 핵심은 구조체의 정렬(alignment)크기를 올바르게 계산하는 것이다.

2. 타입 정보 표현

모든 타입(기본형과 사용자 정의 구조체)은 이름, 크기, 정렬값, 멤버 목록을 갖는다. 기본형 byte, short, int, long은 미리 등록해둔다.

struct TypeDesc {
    std::string name;
    long long size = 0;      // 전체 크기
    long long align = 0;     // 정렬값
    bool primitive = false;  // 기본형 여부
    // 멤버 이름 -> (타입 포인터, 구조체 내 오프셋)
    std::unordered_map<std::string, std::pair<TypeDesc*, long long>> members;

    TypeDesc() = default;
    TypeDesc(std::string n, long long s, long long a)
        : name(std::move(n)), size(s), align(a), primitive(true) {}
};

3. 정렬과 크기 계산

어떤 멤버를 배치할 때 현재 끝 오프셷을 cur, 멤버의 정렬값을 a라 하면, 다음과 같이 춘다.

long long align_up(long long cur, long long a) {
    if (cur % a == 0) return cur;
    return cur + a - (cur % a);
}

구조체의 정렬값은 멤버 정렬값 중 최댓값이며, 구조체 전체 크기는 마지막 멤버가 끝난 위치를 구조체 자신의 정렬값으로 올린 값이다. 멤버 사이의 빈 공간은 자동으로 패딩이 된다.

4. 전체 코드

#include <bits/stdc++.h>
using namespace std;

struct TypeDesc {
    string name;
    long long size = 0;
    long long align = 0;
    bool primitive = false;
    unordered_map<string, pair<TypeDesc*, long long>> members;

    TypeDesc() = default;
    TypeDesc(string n, long long s, long long a)
        : name(std::move(n)), size(s), align(a), primitive(true) {}
};

long long align_up(long long cur, long long a) {
    if (cur % a == 0) return cur;
    return cur + a - (cur % a);
}

unordered_map<string, TypeDesc*> type_table;
unordered_map<string, pair<TypeDesc*, long long>> variables;
long long global_top = 0;

void register_builtin() {
    static TypeDesc b("byte", 1, 1);
    static TypeDesc s("short", 2, 2);
    static TypeDesc i("int", 4, 4);
    static TypeDesc l("long", 8, 8);
    type_table["byte"] = &b;
    type_table["short"] = &s;
    type_table["int"] = &i;
    type_table["long"] = &l;
}

void define_struct(const string& name, int k) {
    TypeDesc* cur = new TypeDesc();
    cur->name = name;
    long long offset = 0;

    for (int i = 0; i < k; ++i) {
        string type_name, member_name;
        cin >> type_name >> member_name;
        TypeDesc* mt = type_table[type_name];

        cur->align = max(cur->align, mt->align);
        offset = align_up(offset, mt->align);
        cur->members[member_name] = {mt, offset};
        offset += mt->size;
    }

    cur->size = align_up(offset, cur->align == 0 ? 1 : cur->align);
    type_table[name] = cur;
    cout << cur->size << ' " << cur->align << "\n";
}

void create_variable(const string& type_name, const string& var_name) {
    TypeDesc* tp = type_table[type_name];
    global_top = align_up(global_top, tp->align);
    variables[var_name] = {tp, global_top};
    cout << global_top << "\n";
    global_top += tp->size;
}

void query_offset(const string& path) {
    vector<string> parts;
    string token;
    istringstream iss(path);
    while (getline(iss, token, '.')) parts.push_back(token);

    long long ans = variables.at(parts[0]).second;
    TypeDesc* cur = variables.at(parts[0]).first;

    for (size_t i = 1; i < parts.size(); ++i) {
        const auto& info = cur->members.at(parts[i]);
        ans += info.second;
        cur = info.first;
    }
    cout << ans << "\n";
}

bool build_path(TypeDesc* type, long long offset, string& out) {
    if (type->primitive) return true;
    for (const auto& [mname, minfo] : type->members) {
        TypeDesc* mt = minfo.first;
        long long moff = minfo.second;
        if (offset >= moff && offset < moff + mt->size) {
            out += "." + mname;
            return build_path(mt, offset - moff, out);
        }
    }
    return false;
}

void query_address(long long addr) {
    for (const auto& [vname, vinfo] : variables) {
        TypeDesc* tp = vinfo.first;
        long long start = vinfo.second;
        if (addr >= start && addr < start + tp->size) {
            string ans = vname;
            if (build_path(tp, addr - start, ans)) {
                cout << ans << "\n";
                return;
            }
            break;
        }
    }
    cout << "ERR\n";
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    register_builtin();

    int n;
    cin >> n;
    while (n--) {
        int op;
        cin >> op;
        if (op == 1) {
            string s; int k;
            cin >> s >> k;
            define_struct(s, k);
        } else if (op == 2) {
            string t, v;
            cin >> t >> v;
            create_variable(t, v);
        } else if (op == 3) {
            string path;
            cin >> path;
            query_offset(path);
        } else {
            long long addr;
            cin >> addr;
            query_address(addr);
        }
    }
    return 0;
}

태그: CSP-S 구조체 메모리 정렬 C++17 오프셋 계산

8월 4일 19:43에 게시됨