24

是否有一套通用规则/指南可以帮助理解何时更喜欢pragma Pure,pragma Preelaborate或其他东西?标准 (Ada 2012)中提出的规则和定义有点繁琐,如果能阅读一些更清晰且面向普通案例的内容,我将不胜感激。

如果我想在不完全理解它的“原因”的情况下做到彻底,我可以简单地尝试:

  • pragma Pure;
  • 如果编译不出来,试试pragma Preelaborate;
  • 如果失败了,那么我做了一些棘手的事情,要么需要逐个pragma Elaborate单元,要么重新考虑包布局。withwith

虽然这可能有效(可以吗?),因为建议尽可能将包标记为 Pure(同样使用 Preelaborate),但它似乎有点脑残,我更愿意更好地理解这个过程。

4

2 回答 2

28

pragma Pure

您应该在任何没有内部状态的包上使用它。它告诉包的用户调用任何子程序都不会产生副作用,因为它们没有可以改变的内部状态。因此,在纯包内的库级别声明的函数在使用相同参数调用时将始终返回相同的结果。

Ada 实现允许缓存纯包的函数的返回值,如果由于这些要求而不会使用子程序的返回值,则可以省略对子程序的调用。但是,您可以通过在纯包中调用导入的子例程(例如,从 C 库中)来违反约束(这些可能会更改 Ada 编译器不知道的某些内部状态)。如果你是邪恶的,你甚至可以从软件的其他部分导入 Ada 子程序pragma Import来绕过pragma Pure. 不用说:如果你正在做这样的事情,不要使用pragma Pure.

编辑:为了澄清可以省略调用的情况,让我引用ARM

如果一个库单元被声明为纯的,那么如果在调用之后不需要结果,则允许实现省略对该库单元的库级子程序的调用。类似地,它可以省略这样的调用,并简单地重用先前对同一子程序的调用产生的结果,前提是没有一个参数是有限类型的,并且所有通过引用的实际参数的地址和值,以及所有 by-copy-in 实际参数的值与之前调用时的值相同。即使子程序在调用时产生其他副作用,此权限也适用。

例如,GNAT 还定义了任何采用类型参数System.Address或派生自它的类型的子程序都不会被视为纯程序,即使它们是在纯程序包中定义的,因为地址指向的位置可能会改变,但 GNAT 会不知道地址指向哪种结构,因此无法检查参数的引用值是否已更改。

pragma Preelaborate

这告诉编译器包不会在精化时执行任何代码(即在主过程开始执行之前)。在细化时,将执行以下构造:

  • 初始化库级变量(这可以是函数调用)
  • 在库级别声明的任务的初始化(它们可能在主过程之前开始执行)
  • begin ... end库级别的块中的语句

如果你不需要它们,你通常应该避免这些东西。pragma Preelaborate尽可能使用,它告诉调用者他可以安全地使用包,而无需在细化时执行任何操作。

如果在您认为应该使用这些 pragma 之一编译某些东西时,请查看它为什么无法编译。它可以帮助您发现包实现或结构的问题。不要在编译不了的时候放弃编译指示。由于约束会影响依赖于您的任何包的可能约束,因此您应该始终选择最严格的适用编译指示。

于 2013-10-14T10:11:24.803 回答
5

GNAT 中的详细订单处理是一个有用的指南。理想情况下,标准规则足以满足大多数程序。编译指示告诉编译器替换您的详细说明顺序。它们应该用于解决具体问题,而不是凭经验使用。

附录:@ajb 强调了编译指示之间的一个重要区别。引用的文章同意问题中概述的方法(第 1 条和第 2 条):“因此,一个好的规则是将单位标记为PurePreelaborate如果可能,如果不可能,则尽可能标记它们Elaborate_Body。” 它继续讨论“这三个 pragma 都不能使用”的情况(第三点)。

于 2013-10-14T10:02:40.933 回答