#P9754. [CSP-S 2023] 结构体

[CSP-S 2023] 结构体

题目背景

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

题目描述

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

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

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

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

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

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

该代码定义了结构体类型 d 与元素 e。元素 e 包含三个成员 e.ae.be.c,分别占据第 010 \sim 1474 \sim 7898 \sim 9 字节的地址。由于类型 d 需要对齐到 44 字节,因此 e 占据了第 0110 \sim 11 字节的地址,大小为 1212 字节。

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

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

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

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

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

输入格式

11 行:一个正整数 nn,表示操作的数量。

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

  • op=1op = 1,首先输入一个字符串 ss 与一个正整数 kk,表示类型名与成员数量,接下来 kk 行每行输入两个字符串 ti,nit_i, n_i,依次表示每个成员的类型与名称。

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

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

  • op=4op = 4,输入一个非负整数 addraddr,表示所访问的地址。

输出格式

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

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

提示

【样例 1 解释】

结构体类型 a 中,short 类型的成员 aa 占据第 010 \sim 1 字节地址,int 类型的成员 ab 占据第 474 \sim 7 字节地址。又由于其对齐要求为 44 字节,可得其大小为 88 字节。由此可同理计算出结构体类型 b 的大小为 1616 字节,对齐要求为 88 字节。

【样例 2】

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

【样例 2 解释】

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

【样例 3】

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

【数据范围】

对于全部数据,满足 1n1001 \le n \le 1001k1001 \le k \le 1000addr10180 \le addr \le 10^{18}

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

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

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

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

测试点 特殊性质
11 A、D
232\sim 3 A
454\sim 5 B、D
686\sim 8 B
9109\sim 10 C、D
111311\sim 13 C
141614\sim 16 D
172017\sim 20

特殊性质 A:没有操作 11

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

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

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

【提示】

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

  • 设该结构体内有 kk 个成员,其大小分别为 s1,...,sks_1,...,s_k,对齐要求分别为 a1,...,aka_1,...,a_k;
  • 则该结构体的对齐要求为 a=max{a1,...,ak}a=\max\{a_1,...,a_k\}
  • 再设这些成员排布时的地址偏移量分别为 o1,...,oko_1,...,o_k,则:
    • o1=0o_1 = 0;
    • 对于 i=2,...,ki=2,...,koio_i 为满足 oi1+si1oio_{i-1}+s_{i-1}\le o_iaia_i 整除 oio_i 的最小值;
    • 则该结构体的大小 ss 为满足 ok+skso_k+s_k\le saa 整除 ss 的最小值;

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

  • 设第 ii 个被定义的元素大小为 sis_i,对齐要求为 aia_i,起始地址为 bib_i;
  • b1=0b_1 = 0,对于 2i2\le ibib_i 为满足 bi1+si1bib_{i-1} + s_{i-1}\le b_iaia_i 整除 bib_i 的最小值。