[CSP-S 2023] 结构体 题解

题目传送门

题面

题目背景

在 C++ 等高级语言中,除了 int 和 float 等基本类型外,通常还可以自定义结构体类型。在本题当中,你需要模拟一种类似 C++ 的高级语言的结构体定义方式,并计算出相应的内存占用等信息。

题目描述

在这种语言中,基本类型共有 4 4 4 种:byteshortintlong,分别占据 1 1 1 2 2 2 4 4 4 8 8 8 字节的空间。

定义一个结构体类型时,需要给出类型名成员,其中每个成员需要按顺序给出类型名称。类型可以为基本类型,也可以为先前定义过的结构体类型。注意,定义结构体类型时不会定义具体元素,即不占用内存。

定义一个元素时,需要给出元素的类型名称。元素将按照以下规则占据内存:

  • 元素内的所有成员将按照定义时给出的顺序在内存中排布,对于类型为结构体的成员同理。
  • 为了保证内存访问的效率,元素的地址占用需要满足对齐规则,即任何类型的大小和该类型元素在内存中的起始地址均应对齐到该类型对齐要求的整数倍。具体而言:
    • 对于基本类型:对齐要求等于其占据空间大小,如 int 类型需要对齐到 4 4 4 字节,其余同理。
    • 对于结构体类型:对齐要求等于其成员的对齐要求的最大值,如一个含有 intshort 的结构体类型需要对齐到 4 4 4 字节。

以下是一个例子(以 C++ 语言的格式书写):

struct d {
    short a;
    int b;
    short c;
};
d e;

该代码定义了结构体类型 d 与元素 e。元素 e 包含三个成员 e.ae.be.c,分别占据第 0 ∼ 1 0 \sim 1 01 4 ∼ 7 4 \sim 7 47 8 ∼ 9 8 \sim 9 89 字节的地址。由于类型 d 需要对齐到 4 4 4 字节,因此 e 占据了第 0 ∼ 11 0 \sim 11 011 字节的地址,大小为 12 12 12 字节。

你需要处理 n n n 次操作,每次操作为以下四种之一:

  1. 定义一个结构体类型。具体而言,给定正整数 k k k 与字符串 s , t 1 , n 1 , … , t k , n k s, t_1, n_1, \dots, t_k, n_k s,t1,n1,,tk,nk,其中 k k k 表示该类型的成员数量, s s s 表示该类型的类型名, t 1 , t 2 , … , t k t_1, t_2, \dots, t_k t1,t2,,tk 按顺序分别表示每个成员的类型, n 1 , n 2 , … , n k n_1, n_2, \dots, n_k n1,n2,,nk 按顺序分别表示每个成员的名称。你需要输出该结构体类型的大小和对齐要求,用一个空格分隔。

  2. 定义一个元素,具体而言,给定字符串 t , n t, n t,n 分别表示该元素的类型与名称。所有被定义的元素将按顺序,从内存地址为 0 0 0 开始依次排开,并需要满足地址对齐规则。你需要输出新定义的元素的起始地址。

  3. 访问某个元素。具体而言,给定字符串 s s s,表示所访问的元素。与 C++ 等语言相同,采用 . 来访问结构体类型的成员。如 a.b.c,表示 a 是一个已定义的元素,它是一个结构体类型,有一个名称为 b 的成员,它也是一个结构体类型,有一个名称为 c 的成员。你需要输出如上被访问的最内层元素的起始地址。

  4. 访问某个内存地址。具体而言,给定非负整数 a d d r addr addr,表示所访问的地址,你需要判断是否存在一个基本类型的元素占据了该地址。若是,则按操作 3 中的访问元素格式输出该元素;否则输出 ERR

输入格式

1 1 1 行:一个正整数 n n n,表示操作的数量。

接下来若干行,依次描述每个操作,每行第一个正整数 o p op op 表示操作类型:

  • o p = 1 op = 1 op=1,首先输入一个字符串 s s s 与一个正整数 k k k,表示类型名与成员数量,接下来 k k k 行每行输入两个字符串 t i , n i t_i, n_i ti,ni,依次表示每个成员的类型与名称。

  • o p = 2 op = 2 op=2,输入两个字符串 t , n t, n t,n,表示该元素的类型与名称。

  • o p = 3 op = 3 op=3,输入一个字符串 s s s,表示所访问的元素。

  • o p = 4 op = 4 op=4,输入一个非负整数 a d d r addr addr,表示所访问的地址。

输出格式

输出 n n n 行,依次表示每个操作的输出结果,输出要求如题目描述中所述。

输入输出样例 #1

输入 #1

5
1 a 2
short aa
int ab
1 b 2
a ba
long bb
2 b x
3 x.ba.ab
4 10

输出 #1

8 4
16 8
0
4
x.bb

说明/提示

【样例 1 解释】

结构体类型 a 中,short 类型的成员 aa 占据第 0 ∼ 1 0 \sim 1 01 字节地址,int 类型的成员 ab 占据第 4 ∼ 7 4 \sim 7 47 字节地址。又由于其对齐要求为 4 4 4 字节,可得其大小为 8 8 8 字节。由此可同理计算出结构体类型 b 的大小为 16 16 16 字节,对齐要求为 8 8 8 字节。

【样例 2】

见选手目录下的 struct/struct2.in 与 struct/struct2.ans。

【样例 2 解释】

第二个操作 4 中,访问的内存地址恰好在为了地址对齐而留下的 “洞” 里,因此没有基本类型元素占据它。

【样例 3】

见选手目录下的 struct/struct3.in 与 struct/struct3.ans。

【数据范围】

对于全部数据,满足 1 ≤ n ≤ 100 1 \le n \le 100 1n100 1 ≤ k ≤ 100 1 \le k \le 100 1k100 0 ≤ a d d r ≤ 1 0 18 0 \le addr \le 10^{18} 0addr1018

所有定义的结构体类型名、成员名称和定义的元素名称均由不超过 10 10 10 个字符的小写字母组成,且都不是 byte,short,int,long(即不与基本类型重名)。

所有定义的结构体类型名和元素名称互不相同,同一结构体内成员名称互不相同。但不同的结构体可能有相同的成员名称,某结构体内的成员名称也可能与定义的结构体或元素名称相同。

保证所有操作均符合题目所述的规范和要求,即结构体的定义不会包含不存在的类型、不会访问不存在的元素或成员等。

保证任意结构体大小及定义的元素占据的最高内存地址均不超过 1 0 18 10^{18} 1018

测试点特殊性质
1 1 1A、D
2 ∼ 3 2\sim 3 23A
4 ∼ 5 4\sim 5 45B、D
6 ∼ 8 6\sim 8 68B
9 ∼ 10 9\sim 10 910C、D
11 ∼ 13 11\sim 13 1113C
14 ∼ 16 14\sim 16 1416D
17 ∼ 20 17\sim 20 1720

特殊性质 A:没有操作 1 1 1

特殊性质 B:只有一个操作 1 1 1

特殊性质 C:所有操作 1 1 1 中给出的成员类型均为基本类型;

特殊性质 D:基本类型只有 long

【提示】

对于结构体类型的对齐要求和大小,形式化的定义方式如下:

  • 设该结构体内有 k k k 个成员,其大小分别为 s 1 , . . . , s k s_1,...,s_k s1,...,sk,对齐要求分别为 a 1 , . . . , a k a_1,...,a_k a1,...,ak;
  • 则该结构体的对齐要求为 a = max ⁡ { a 1 , . . . , a k } a=\max\{a_1,...,a_k\} a=max{a1,...,ak}
  • 再设这些成员排布时的地址偏移量分别为 o 1 , . . . , o k o_1,...,o_k o1,...,ok,则:
    • o 1 = 0 o_1 = 0 o1=0;
    • 对于 i = 2 , . . . , k i=2,...,k i=2,...,k o i o_i oi 为满足 o i − 1 + s i − 1 ≤ o i o_{i-1}+s_{i-1}\le o_i oi1+si1oi a i a_i ai 整除 o i o_i oi 的最小值;
    • 则该结构体的大小 s s s 为满足 o k + s k ≤ s o_k+s_k\le s ok+sks a a a 整除 s s s 的最小值;

对于定义元素时的内存排布,形式化的定义方式如下:

  • 设第 i i i 个被定义的元素大小为 s i s_i si,对齐要求为 a i a_i ai,起始地址为 b i b_i bi;
  • b 1 = 0 b_1 = 0 b1=0,对于 2 ≤ i 2\le i 2i b i b_i bi 为满足 b i − 1 + s i − 1 ≤ b i b_{i-1} + s_{i-1}\le b_i bi1+si1bi a i a_i ai 整除 b i b_i bi 的最小值。

struct.zip

思路

S 组大模拟。。。

存储

先考虑如何存储定义的结构体和元素。显然我们应当用结构体。我们可以定义以下结构体存储定义的类型。

struct TYPE
{
	string name;//类型名称
	vector <TYPE*> son_types;//类型所包含的类型的指针
	vector <string> son_names;//类型所包含的类型的名称
	int memory;//类型占用空间
	int align;//类型对齐要求
};
TYPE types[110];
map <string,TYPE*> name_type;//类型名->类型指针

注意要先把基础类型存入 type 数组,基础类型不包含类型,占用空间和对齐要求相同。

name_type["byte"] = &types[1];//&存指针
name_type["short"] = &types[2];
name_type["int"] = &types[3];
name_type["long"] = &types[4];
types[1].name = "byte";
types[2].name = "short";
types[3].name = "int";
types[4].name = "long";
types[1].memory = types[1].align = 1;
types[2].memory = types[2].align = 2;
types[3].memory = types[3].align = 4;
types[4].memory = types[4].align = 8;

然后对于元素,我们定义以下结构体:

struct ELEMENT
{
	TYPE type;//元素类型
	string name;//元素名称
	int st_addr;//元素起始内存
};
ELEMENT elements[102];
map <string,ELEMENT*> name_element;//元素名->元素指针
map <int,ELEMENT*> addr_element;//地址位置->元素指针

还有 3 3 3 个重要的变量:

int typesum //当前类型数
,elementsum //当前元素数
,nowmemory; //下一个元素的地址

这样我们就模拟出了存储系统。

操作 1 1 1

对于操作 1 1 1 ,我们先定义一个函数:

int find_align(int now,int align)
{
	if(now % align) return (now / align + 1) * align;
	return now;

这个函数求出当前地址为 n o w now now 时,对齐到 a l i g n align align 后的位置。

然后直接模拟即可。

case 1:
{
	string name;
	int classes,memory = 0,align = 0;
	cin >> name >> classes;
	name_type[name] = &types[++typesum];
	types[typesum].name = name;
	for(int i = 1;i <= classes;i++)
	{
		string son_type,son_name;
		cin >> son_type >> son_name;
		types[typesum].son_types.push_back(name_type[son_type]);
		types[typesum].son_names.push_back(son_name);
		memory = find_align(memory,name_type[son_type]->align) + name_type[son_type]->memory;
		align = max(align,name_type[son_type]->align);
	}
	memory = find_align(memory,align);
	types[typesum].memory = memory;
	types[typesum].align = align;
	cout << memory << ' ' << align << '\n';
	break;
}

这里注意 name_type[x] 对应的是一个指针,所以不能用 . 访问子类型,要用 (*name_type[x]).name_type[x]-> 来访问。

操作 2 2 2

和操作 1 1 1 同理,注意维护对齐和地址。然后把起始位置存入地址到元素的映射。

case 2:
{
	string t,n;
	cin >> t >> n;
	cout << find_align(nowmemory,name_type[t]->align) << '\n';
	elements[++elementsum] = {*name_type[t],n,find_align(nowmemory,name_type[t]->align)};
	name_element[n] = &elements[elementsum];
	addr_element[nowmemory] = &elements[elementsum];
	nowmemory = find_align(nowmemory,name_type[t]->align) + name_type[t]->memory;
	break;
}

操作 3 3 3

这里我们用一个队列,按 . 把输入的字符串分割。然后每次取队列头,遍历得出内存。同样注意对齐。

case 3:
{
	string s,ele;
	queue <string> find;
	cin >> s;
	s += '.';//得到最后一串
	for(auto i : s)
	{
		if(i == '.')
		{
			find.push(ele);
			ele.clear();
		}
		else ele += i;
	}
	int addr = name_element[find.front()]->st_addr;
	TYPE x = name_element[find.front()]->type;
	find.pop();
	while(find.size())
	{
		string now = find.front();
		find.pop();
		for(int i = 0;i < (int)x.son_types.size();i++)
		{
			addr = find_align(addr,x.son_types[i]->align);
			if(x.son_names[i] == now)
			{
				x = *x.son_types[i];
				break;
			}
			addr += x.son_types[i]->memory;
		}
	}
	cout << addr << '\n';
	break;
}

操作 4 4 4

根据从地址到元素的映射找到当前地址所在的元素,然后和操作 3 3 3 一样循环求解。

case 4:
{
	int addr;
	bool undef = 0;
	cin >> addr;
	if(addr >= nowmemory)
	{
		cout << "ERR\n";
		break;
	}
	ELEMENT loc;
	TYPE nowtp;
	for(auto i : addr_element)
	{
		if(i.first > addr) break;
		loc = *i.second;
	}
	string ans = loc.name;
	int begaddr = loc.st_addr;
	nowtp = loc.type;
	while(nowtp.son_types.size())
	{
		int parent_base = begaddr;
		for(int i=0; i<nowtp.son_types.size(); i++)
		{
			int beginaddr = find_align(parent_base,nowtp.son_types[i]->align);
			int endaddr = beginaddr + nowtp.son_types[i]->memory;
			if(beginaddr <= addr && addr < endaddr)
			{
				ans += '.' + nowtp.son_names[i];
				begaddr = beginaddr;
				nowtp = *nowtp.son_types[i];
				parent_base = begaddr;
				break;
			}
			else
			{
				if(i + 1 == nowtp.son_types.size())
				{
					cout << "ERR\n";
					undef = 1;
					break;
				}
			}
			parent_base = endaddr;
		}	
		if(undef) break;
	}
	if(!nowtp.son_types.size())
	{
		if(begaddr > addr)
		{
			cout << "ERR\n";
			undef = 1;
		}
	}
	if(!undef) cout << ans << '\n';
	break;
}

最终代码

只要沉下心来认真 debug ,就可以 AC。

#include<iostream>
#include<cstring>
#include<vector>
#include<queue>
#include<map>
#define int long long
using namespace std;
int n;
struct TYPE
{
	string name;
	vector <TYPE*> son_types;
	vector <string> son_names;
	int memory;
	int align;
};
struct ELEMENT
{
	TYPE type;
	string name;
	int st_addr;
};
TYPE types[110];
ELEMENT elements[110];
map <string,TYPE*> name_type;
map <string,ELEMENT*> name_element;
map <int,ELEMENT*> addr_element;
int typesum,elementsum,nowmemory;
int find_align(int now,int align)
{
	if(now % align) return (now / align + 1) * align;
	return now;
}
signed main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	name_type["byte"] = &types[1];
	name_type["short"] = &types[2];
	name_type["int"] = &types[3];
	name_type["long"] = &types[4];
	types[1].name = "byte";
	types[2].name = "short";
	types[3].name = "int";
	types[4].name = "long";
	types[1].memory = types[1].align = 1;
	types[2].memory = types[2].align = 2;
	types[3].memory = types[3].align = 4;
	types[4].memory = types[4].align = 8;
	typesum = 4;
	cin >> n;
	while(n--)
	{
		int op;
		cin >> op;
		switch(op)
		{
		case 1:
			{
				string name;
				int classes,memory = 0,align = 0;
				cin >> name >> classes;
				name_type[name] = &types[++typesum];
				types[typesum].name = name;
				for(int i = 1;i <= classes;i++)
				{
					string son_type,son_name;
					cin >> son_type >> son_name;
					types[typesum].son_types.push_back(name_type[son_type]);
					types[typesum].son_names.push_back(son_name);
					memory = find_align(memory,name_type[son_type]->align) + name_type[son_type]->memory;
					align = max(align,name_type[son_type]->align);
				}
				memory = find_align(memory,align);
				types[typesum].memory = memory;
				types[typesum].align = align;
				cout << memory << ' ' << align << '\n';
				break;
			}
		case 2:
			{
				string t,n;
				cin >> t >> n;
				cout << find_align(nowmemory,name_type[t]->align) << '\n';
				elements[++elementsum] = {*name_type[t],n,find_align(nowmemory,name_type[t]->align)};
				name_element[n] = &elements[elementsum];
				addr_element[nowmemory] = &elements[elementsum];
				nowmemory = find_align(nowmemory,name_type[t]->align) + name_type[t]->memory;
				break;
			}
		case 3:
			{
				string s,ele;
				queue <string> find;
				cin >> s;
				s += '.';
				for(auto i : s)
				{
					if(i == '.')
					{
						find.push(ele);
						ele.clear();
					}
					else ele += i;
				}
				int addr = name_element[find.front()]->st_addr;
				TYPE x = name_element[find.front()]->type;
				find.pop();
				while(find.size())
				{
					string now = find.front();
					find.pop();
					for(int i = 0;i < (int)x.son_types.size();i++)
					{
						addr = find_align(addr,x.son_types[i]->align);
						if(x.son_names[i] == now)
						{
							x = *x.son_types[i];
							break;
						}
						addr += x.son_types[i]->memory;
					}
				}
				cout << addr << '\n';
				break;
			}
		case 4:
			{
				int addr;
				bool undef = 0;
				cin >> addr;
				if(addr >= nowmemory)
				{
					cout << "ERR\n";
					break;
				}
				ELEMENT loc;
				TYPE nowtp;
				for(auto i : addr_element)
				{
					if(i.first > addr) break;
					loc = *i.second;
				}
				string ans = loc.name;
				int begaddr = loc.st_addr;
				nowtp = loc.type;
				while(nowtp.son_types.size())
				{
					int parent_base = begaddr;
					for(int i=0; i<nowtp.son_types.size(); i++)
					{
						int beginaddr = find_align(parent_base,nowtp.son_types[i]->align);
						int endaddr = beginaddr + nowtp.son_types[i]->memory;
						if(beginaddr <= addr && addr < endaddr)
						{
							ans += '.' + nowtp.son_names[i];
							begaddr = beginaddr;
							nowtp = *nowtp.son_types[i];
							parent_base = begaddr;
							break;
						}
						else
						{
							if(i + 1 == nowtp.son_types.size())
							{
								cout << "ERR\n";
								undef = 1;
								break;
							}
						}
						parent_base = endaddr;
					}
					
					if(undef) break;
				}
				if(!nowtp.son_types.size())
				{
					if(begaddr > addr)
					{
						cout << "ERR\n";
						undef = 1;
					}
				}
				if(!undef) cout << ans << '\n';
				break;
			}
		}
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值