有没有一套一般规则/指南可以帮助理解什么时候更喜欢pragma Pure
、pragma Preelaborate
或其他完全不同的东西?标准(Ada 2012)中提出的规则和定义有点沉重,我希望能读到一些更清晰且针对普通情况的内容。
如果我想在不完全理解它的"原因"的情况下彻底,我可以简单地尝试一下:
- 用
pragma Pure;
标记软件包规格 - 如果无法编译,请尝试
pragma Preelaborate;
- 如果失败了,那么我做了一些棘手的事情,要么需要
with
with
地pragma Elaborate
单元,要么重新考虑包布局。
虽然这可能有效(是吗?),因为建议尽可能将包标记为 Pure(与 Preeelaborate 一样),但它似乎有点脑损伤,我更愿意更好地理解这个过程。
pragma Pure
您应该在任何没有内部状态的软件包上使用它。它告诉包的用户,对任何子程序的调用都不会产生副作用,因为它们没有可以更改的内部状态。因此,在纯包中的库级别声明的函数在使用相同的参数调用时将始终返回相同的结果。
Ada 实现允许缓存纯包函数的返回值,如果由于这些要求而不使用子例程的返回值,则省略对子例程的调用。但是,您可以通过在纯包中调用导入的子例程(例如来自 C 库)来违反约束(这些子例程可能会更改 Ada 编译器不知道的一些内部状态)。如果你是邪恶的,你甚至可以从软件的其他部分导入 Ada 子程序,pragma Import
绕过pragma Pure
的要求。不用说:如果你正在做这样的事情,不要使用pragma Pure
。
编辑:为了澄清可以省略调用的情况,让我引用 ARM:
如果库单元被声明为纯库单元,则允许实现省略对库单元的库级子程序的调用,前提是调用后不需要结果。类似地,它可以省略此类调用,而只是重用对同一子程序的早期调用生成的结果,前提是没有一个参数属于受限类型,并且所有按引用实际参数的地址和值以及所有按复制的实际参数的值与先前调用时相同。即使子程序在调用时产生其他副作用,此权限也适用。
例如,GNAT 还定义了任何采用 System.Address
类型参数或派生自该参数的子例程,即使它们是在纯包中定义的,也不被视为纯子例程,因为地址指向的位置可能会更改,但 GNAT 不知道地址指向哪种结构,因此无法运行任何检查参数的引用值是否已更改。
pragma Preelaborate
这告诉编译器包不会在详细说明时(即在主过程开始执行之前)执行任何代码。在详细说明时,将执行以下构造:
- 库级变量的初始化(这可以是函数调用) 在
- 库级别声明的任务的初始化(它们可以在主过程之前开始执行)
- 库级别
begin ... end
块中的语句
如果你不需要它们,你通常应该避免这些东西。尽可能使用 pragma Preelaborate
,它告诉调用方他可以安全地使用该包,而无需在详细说明时执行任何操作。
指示之一进行编译,请查看它无法编译的原因。它可以帮助您发现包实现或结构的问题。不要只是在编译不编译时删除编译指示。由于约束会影响依赖于您的任何包的可能约束,因此应始终选择最严格的适用编译指示。
GNAT 中的细化订单处理是一个有用的指南。理想情况下,标准规则足以满足大多数程序的需求。编译指示告诉编译器替换您的详细说明顺序。它们应该用于解决具体问题,而不是经验使用。
增编:@ajb强调了这些编译指示之间的重要区别。引用的文章同意问题中概述的方法(项目符号一和项目二):"因此,一个好的规则是尽可能将单位标记为Pure
或Preelaborate
,如果不可能,则尽可能将它们标记为Elaborate_Body
。 它继续讨论"这三个编译指示都不能使用的情况(第三点符号)。