用 grammars-v4 测试套件验证 C 编译器结构体支持:chapter_18 `no_structure_parameters` 测试组织与调试指南
2026/9/24 14:16:03 网站建设 项目流程
  • 编程语言
  • 编译器
  • 开发工具

【免费下载链接】grammars-v4

Grammars written for ANTLR v4; expectation that the grammars are free of actions.

项目地址:https://gitcode.com/gh_mirrors/gr/grammars-v4
点击查看免费下载

导读

在编写 C 编译器时,结构体(struct)支持是最具挑战性的里程碑之一:它同时涉及类型系统(标签解析、类型完备性)、代码生成(大小与成员偏移计算)、表达式语义(./->成员访问)以及存储语义(自动/静态初始化)。本文基于 grammars-v4 仓库中随附的 C 编译器测试套件(writing-a-c-compiler-tests)第 18 章的子集——no_structure_parameters/README.md——系统讲解该测试目录的组织方式、每一类测试的验证目标,以及推荐的调试顺序。读完本文,你将掌握如何按图索骥地使用这些测试用例排查结构体类型声明、初始化、成员访问、大小计算等功能的实现缺陷。

测试范围界定:本目录验证什么、不验证什么

第 18 章valid目录下按结构体参数支持程度分成了三组,本 README 只管辖其中一组——no_structure_parameters/。其边界定义非常明确(见 README.md):

  • 不验证:把整个结构体作为函数参数传递,或作为返回值返回;
  • 允许:传递"指向结构体的指针"作为参数或返回值(这类用例也出现在libraries/中);
  • 验证:结构体类型声明、复合初始化(compound initializer)、成员访问(.->)、结构体整体复制等完整基础能力。

换句话说,这个目录是一个"结构体支持前半程"的验收关卡——在实现"按值传结构体/返回结构体"(对应同级的 parameters 与 params_and_returns)之前,必须先通过这些测试。

配套的语法层面支撑可以在仓库的 CParser.g4 与 CLexer.g4 中查看(structOrUnionSpecifierstructDeclarationpostfixExpression等规则定义了结构体声明与./->成员访问的语法)。测试套件中的invalid_*目录则从反面验证语法与类型检查的健壮性。

测试目录全景:七个维度的分层验证

no_structure_parameters/下共 53 个文件(42 个.c、10 个.h、1 个.md),按测试目的分为 7 个目录:

目录核心验证目标典型文件
smoke_tests/结构体类型声明、复合初始化、成员访问的冒烟测试simple.c,static_vs_auto.c
parse_and_lex/解析器与词法分析器对边界情况的接受度postfix_precedence.c,trailing_comma.c
semantic_analysis/标识符解析与类型检查(标签解析、类型完备性、不完整类型)resolve_tags.c,incomplete_structs.c
size_and_offset_calculations/结构体大小与成员偏移计算sizeof_exps.c,struct_sizes.h
scalar_member_access/.->运算符的标量成员访问dot.c,arrow.c,nested_struct.c
struct_copy/结构体整体复制(赋值、经./->/[]读写)copy_struct.c,stack_clobber.c
libraries/多翻译单元测试(全局结构体、初始化、指针参数/返回值)global_struct.c,opaque_struct.c

值得注意的是,README 特别说明size_and_offset_calculations/中的测试"只通过&sizeof运算符计算大小与偏移,不实际读写任何结构体或成员"——这意味着它们可以独立于内存读写代码生成正确性,先验证布局计算。

smoke_tests:从最小用例出发

这是推荐的第一个调试目标,用于确认最基础的能力链路是否打通。simple.c 展示了最小闭环:

struct pair { int a; int b; }; int main(void) { struct pair x = {1, 2}; // 声明 + 复合初始化 if (x.a != 1 || x.b != 2) { // . 运算符读取 return 1; } struct pair *x_ptr = &x; if (x_ptr->a != 1 || x_ptr->b != 2) { // -> 运算符读取 return 2; } return 0; }

static_vs_auto.c 则进一步验证存储期初始化语义:在循环中同时声明struct s autom = {1, 2};(自动存储期)与static struct s stat = {1, 2};(静态存储期),每次迭代都自增成员。到第 10 次迭代时:

  • stat的成员被递增了 10 次,期望值为{11, 12}——验证静态结构体只初始化一次
  • autom每次进入作用域被重新初始化,期望值为{2, 3}——验证自动结构体每次进入作用域都重新初始化

这一用例直接检验了存储分配与初始化代码生成的正确区分,是排查"初始化时机错误"类缺陷的关键样本。

size_and_offset_calculations:布局计算的黄金样本

该目录使用共享头文件 struct_sizes.h 定义了一组精心设计的结构体,覆盖了丰富的布局场景:

  • 成员对齐与尾部填充struct eight_bytesint+char组成,末尾补 3 字节使总大小成为 4 的倍数(8 字节);
  • 嵌套结构体与内部填充struct sixteen_bytes嵌套了 8 字节、2 字节、3 字节三个子结构体,合计 13 字节后再补 3 字节到 16 字节;
  • 成员间填充struct twentyfour_bytesseven(7 字节)后需补 1 字节,才能让 4 字节对齐的sixteen从偏移 8 开始;
  • 非 2 的幂大小struct wonky是 19 字节的char数组,无填充;
  • 更大对齐成员struct internal_paddingchar后补 7 字节,使double8 字节对齐,总大小 16 字节。

sizeof_exps.c 则对表达式(而非仅类型名)应用sizeof,逐一断言期望值,例如:

  • sizeof arr_struct.struct_array[2]应等于 8(数组元素类型struct eight_bytes的大小)——验证下标表达式类型推断
  • sizeof arr_struct.struct_array应等于 24 而非经数组退化后的 8——显式验证数组在sizeof中不发生退化(注释中专门强调 "make sure arr_struct.struct_array doesn't undergo array decay here");
  • sizeof *get_twentybyte_ptr()应等于 20——验证函数返回指针的解引用类型推断

member_offsets.cmember_comparisons.c则通过&运算符比较成员地址,进一步校验每个成员的偏移量是否符合上述布局预期。

scalar_member_access:.->的读写闭环

dot.c 以struct four_members { double d; char c; long l; char *ptr; }为对象,系统验证了:

  • 写成员:将复杂表达式(如函数调用get_double() * 2.0、静态全局变量除法l / 4)的结果赋给成员;
  • 读成员:直接比较成员值;
  • 取成员地址&autom.d赋给double *后解引用;
  • 成员参与函数调用:把成员(含派生值)作为实参传入accept_params,在函数内校验;
  • 同一套用例分别在自动存储期test_auto)与静态存储期test_static)结构体上各跑一遍。

nested_struct.clinked_list.carrow.c则覆盖嵌套标量成员访问与链表式->链式访问,但正如 README 所说,"none require copying an entire structure to another location"——嵌套访问只取标量成员,不整体搬移结构体。

struct_copy:结构体整体复制语义

这是本目录中功能上最"重"的一类:验证把整个结构体从一个位置复制到另一个位置的能力。struct_copy/ 中的用例包括:

  • copy_struct.c:结构体变量之间的整体赋值;
  • copy_struct_through_pointer.c:经指针间接复制;
  • copy_struct_with_dot_operator.c/copy_struct_with_arrow_operator.c:通过.->运算符读写整个结构体成员(成员本身是结构体);
  • stack_clobber.c:与[]运算符配合的整体读写。

共享的structs.h定义被测类型。这些用例实际上是"按值传参/返回"的前置验证——只有结构体能整体复制,params_and_returns/中的高级特性才有实现基础。

semantic_analysis:标签解析与类型完备性

resolve_tags.c 是本章最全面的类型系统用例之一,覆盖了结构体标签(tag)在不同上下文中的解析

  • 变量声明中的标签遮蔽:内层作用域定义同名struct shadow后,外层变量outer仍保留原类型,内外成员均可访问;
  • 成员声明中的自引用struct s *self_ptr;在成员声明中指向正在定义的这个struct s,而非文件作用域的同名类型;
  • 函数声明中的标签解析:内层struct s离开作用域后,函数声明自动回退到外层类型;
  • for 循环头中的结构体声明for (struct s loop_struct = {10}; ...)与循环体内的同名新类型共存;
  • cast 表达式与 sizeof 中的标签sizeof(struct s)在内层是 15 字节、外层是 8 字节,验证标签按作用域解析;
  • 派生类型struct s *(*outer_arr)[3]这类"指向 3 个指针数组的指针"中的标签解析;
  • 无成员列表的标签声明是无操作struct s;若当前作用域已声明过该标签,则什么都不做。

配合incomplete_structs.cnamespaces.c(结构体标签与普通标识符位于不同命名空间)与cast_struct_to_void.c,共同验证"标签解析、类型完备性跟踪、不完整结构体的合法使用"。

parse_and_lex:语法层的边界情况

该目录的用例保证词法与语法分析器不误拒合法代码,例如:

  • postfix_precedence.c:验证后缀运算符优先级高于前缀——-array[2].b.inner_arr[1]实际是-(array[2].b.inner_arr[1]),同时覆盖结构体数组的复合初始化(嵌套{{...}}初始化器);
  • space_around_struct_member.c.->两侧空白的宽容处理;
  • struct_member_looks_like_const.c:成员名恰好看似关键字/常量名时的消歧;
  • trailing_comma.c:初始化列表尾部逗号的合法接受。

libraries:多翻译单元下的结构体

libraries/模拟真实工程的链接场景,包含.c与配套.h的成对用例:

  • 全局结构体跨翻译单元:global_struct.c 中update_struct读写头文件声明的全局结构体成员,global_struct_client.c从另一个翻译单元访问同一结构体;
  • 结构体指针参数/返回值param_struct_pointer.creturn_struct_pointer.c验证指针传参与返回(这是本目录允许的边界);
  • 不透明结构体opaque_struct.c验证"声明了标签但成员不可见"的不完整类型场景;
  • 初始化器位置语义libraries/initializers/下的四组用例(auto_struct_initializersstatic_struct_initializers及各自的nested_*变体)专门验证结构体初始化器在正确位置写入正确的值,覆盖自动/静态存储期与嵌套初始化。

推荐的调试顺序与使用建议

README 给出的调试顺序本身就是一个"从浅到深、先易后难"的排错路线图:

  1. smoke_tests/—— 先打通声明、初始化、成员访问的最小链路;
  2. size_and_offset_calculations/—— 独立验证布局计算(不依赖内存读写);
  3. scalar_member_access/—— 验证./->的完整读写与寻址;
  4. libraries/initializers/—— 提前验证初始化器语义;
  5. parse_and_lex/—— 补齐语法层边界;
  6. struct_copy/—— 攻坚整体复制;
  7. semantic_analysis/—— 最后系统验证类型系统细节;
  8. libraries/其余用例 —— 收尾验证多翻译单元。

调试时建议遵循以下原则:

  • 每个测试文件都是独立的可执行程序:约定返回 0 表示成功、非 0 表示失败(返回码还用于区分不同的失败分支),可以直接编译运行逐一验证;
  • 先跑冒烟再跑语义:语法/词法问题会阻塞所有后续阶段,因此parse_and_lex虽靠后列出,但如果冒烟测试都过不了,应优先排查语法规则;
  • 善用invalid_对照:本章同级目录(如 invalid_parse、invalid_types)提供了应当报错的用例,与valid/形成对照,可用于回归测试"误接受"与"误拒绝"两类缺陷;
  • 以 ANTLR 语法为参照:当某个用例解析失败时,对照 CParser.g4 检查structDeclarationinitDeclaratorListpostfixExpression等规则是否覆盖了对应语法形态。

小结

no_structure_parameters/是一个精心分层、边界清晰的 C 编译器结构体功能测试集:它把"结构体支持"拆解为冒烟、解析、语义、布局、成员访问、整体复制与多翻译单元七个可独立调试的维度,并明确划出"不传结构体值参/返回值"这一阶段边界。按照 README 给出的顺序逐层推进,配合invalid_*对照用例与 CParser.g4 语法参照,可以系统性地定位结构体相关实现中的每一类缺陷,为后续实现按值传参/返回结构体(parameters/ 与 params_and_returns/)打下坚实基础。

  • 编程语言
  • 编译器
  • 开发工具

【免费下载链接】grammars-v4

Grammars written for ANTLR v4; expectation that the grammars are free of actions.

项目地址:https://gitcode.com/gh_mirrors/gr/grammars-v4
点击查看免费下载

相关推荐

上一篇:最完整解析:Binwalk项目在Python 3.12环境下的兼容性问题与解决方案
下一篇:FluentFlyout 媒体控制功能详解:封面显示、播放控制与音频管理

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询