在书籍、stackoverflow 和一般网络上搜索了一段时间后,我发现很难找到对 fortran 参数意图之间真正差异的直接解释。我的理解方式是这样的:

  • intent(in) -- 实际参数在输入时被复制到虚拟参数。
  • intent(out) -- 虚拟参数指向实际参数(它们都指向内存中的同一位置)。
  • intent(inout) -- 虚拟参数在本地创建,然后在过程完成时复制到实际参数。

如果我的理解是正确的,那么我也想知道为什么有人想要使用 intent(out), ,自从 intent(inout) 需要更少的工作(无需复制数据)。

有帮助吗?

解决方案

意图只是编译器的提示,您可以丢弃该信息并违反它。意图的存在几乎完全是为了确保您只执行计划在子例程中执行的操作。编译器可能会选择信任您并优化某些内容。

这意味着 intent(in) 不是按值传递。您仍然可以覆盖原始值。

program xxxx  
    integer i  
    i = 9  
    call sub(i)  
    print*,i ! will print 7 on all compilers I checked  
end  
subroutine sub(i)  
    integer,intent(in) :: i  
    call sub2(i)  
end  
subroutine sub2(i)  
    implicit none  
    integer i  
    i = 7  ! This works since the "intent" information was lost.  
end

program xxxx  
    integer i  
    i = 9  
    call sub(i)  
end  
subroutine sub(i)  
    integer,intent(out) :: i  
    call sub2(i)  
end  
subroutine sub2(i)  
    implicit none   
    integer i  
    print*,i ! will print 9 on all compilers I checked, even though intent was "out" above.  
end  

其他提示

  • intent(in) - 看起来像按值传递(并且对此的更改不会反映在外部代码中),但实际上是按引用传递,并且编译器禁止更改它。但它仍然可以改变。
  • intent(out) - 以某种方式通过引用传递,实际上是一个返回参数
  • intent(inout) - 通过引用传递,正常输入/输出参数。

使用 intent(out) 如果很清楚,请记录您的设计。如果有的话,不要关心很小的性能增益。(评论表明没有 intent(in) 从技术上讲也是通过引用传递。)

目前还不清楚OP的部分问题是否得到了真正的回答。此外,随后的答案/讨论中似乎存在很多混乱和各种错误,可能会从一些澄清中受益。

A)OP的问题Re

“那么我还想知道为什么人们想要使用 Intent(out),因为 Intent(inout) 需要更少的工作(无需复制数据)。”

可能没有回答,或者至少回答得太直接/正确。

首先,要明确的是 Intent 属性至少有两个用途:“安全/卫生”和“间接性能”问题(不是“直接性能”问题)。

1) 安全/卫生:协助生成“安全/合理”的代码,减少“搞乱事情”的机会。因此,Intent(In) 不能被覆盖(至少在本地,或者在某些情况下甚至是“全局”,见下文)。

类似地,Intent(Out) 要求为 Arg 分配一个“明确的答案”,从而有助于减少“垃圾”结果。

例如,在解决计算数学中可能最常见的问题时,即所谓“Ax=b 问题”,所寻找的“直接结果/答案”是向量 x 的值。这些应该是 Intent(Out) 以确保为 x 分配一个“明确”的答案。如果 x 被声明为 Intent(InOut) 或“no Intent”,那么 Fortran 将为 x 分配一些“默认值”(在调试模式下可能是“零”,但在发布模式下可能是“垃圾”,无论是在内存中的 Args 指针位置),如果用户没有明确地将正确的值分配给 x,它将返回“垃圾”。Intent(Out) 将“提醒/强制”用户显式地为 x 赋值,从而避免这种“(意外的)垃圾”。

在求解过程中,(几乎肯定)会产生矩阵 A 的逆矩阵。用户可能希望返回调用 s/r 的逆函数来代替 A,在这种情况下,A 应该是 Intent(InOut)。

或者,用户可能希望确保不对矩阵 A 或向量 b 进行任何更改,在这种情况下,它们将被声明为 Intent(In),从而确保临界值不被覆盖。

2 a) “间接绩效”(和“全球安全/卫生”):尽管意图并不直接影响性能,但它们是间接影响性能的。值得注意的是,某些类型的优化,特别是 Fortran Pure 和 Elemental 结构,可以大大提高性能。这些设置通常要求所有参数显式声明其意图。

粗略地说,如果编译器提前知道所有变量的意图,那么它可以更轻松、更有效地优化和“愚蠢检查”代码。

至关重要的是,如果使用 Pure 等构造,那么很有可能也会存在一种“全局安全/卫生”,因为 Pure/Elemental s/p 只能调用其他 Pure/Elemental s/p,所以无法达到“The Glazer Guy's”示例中所示的那种情况。

例如,如果 Sub1() 声明为 Pure,那么 Sub2() 也必须声明为 Pure,然后就需要声明各个级别的 Intents,因此“The Glazer Guy's”中产生的“垃圾出” “这个例子不可能发生。也就是说,代码将是:

Pure subroutine sub_P(i)
    integer,intent(in) :: i
    call sub2_P(i)
end  subroutine sub_P

Pure subroutine sub2_P(i)
    implicit none
!        integer i          ! not permitted to omit Intent in a Pure s/p
    integer,intent(in) :: i
    i = 7   ! This WILL NOT WORK/HAPPEN, since Pure obviates the possibility of omitting Intent, and Intent(In) prohibits assignment ... so "i" remains "safe".
end  subroutine sub2_P

...编译时,这会产生类似的结果

“||错误:在变量定义上下文(分配)at(1)|中有意图(in)的虚拟参数“ i” | “

当然,sub2 不必是 Pure 即可将 i 声明为 Intent(In),这将再次提供人们所寻求的“安全/卫生”。

请注意,即使我被声明为 Intent(InOut),它仍然会因 Pure 的而失败。那是:

Pure subroutine sub_P(i)
    integer,intent(in) :: i
    call sub2_P(i)
end  subroutine sub_P

Pure subroutine sub2_P(i)
    implicit none
    integer,intent(inOut) :: i
    i = 7   ! This WILL NOT WORK, since Pure obviates the possibility of "mixing" Intent's.
end  subroutine sub2_P

...编译时,这会产生类似的结果

“||错误:变量定义上下文中带有 INTENT(IN) 的虚拟参数“i”(INTENT = OUT/INOUT 的实际参数)位于 (1)|”

因此,严格或广泛依赖纯/元素结构将确保(主要)“全球安全/卫生”。

不可能在所有情况下都使用纯/元素等(例如许多混合语言设置,或者依赖超出您控制的外部库时等)。

尽管如此,一致使用 Intent 以及尽可能使用 Pure 等,将产生很多好处,并消除很多悲伤。

人们可以简单地养成在可能的情况下始终在任何地方声明 Intent 的习惯,无论是否纯粹......这是推荐的编码实践。

...这也凸显了 Intent(InOut) 和 Intent(Out) 存在的另一个原因,因为 Pure 必须声明所有 Arg 的 Intent,所以会有一些 Args 仅是 Out,而其他 Args 是 InOut(即如果没有 In、InOut 和 Out 意图,就很难拥有 Pure 的)。

2 b) OP 的评论期望“性能改进”,因为不需要复制”表明对 Fortran 及其广泛使用引用传递的误解。通过引用传递意味着,本质上只需要指针,事实上,通常只需要指向数组中第一个元素的指针(加上一些隐藏的数组信息)。

事实上,通过考虑“过去的日子”(例如,Fortran IV、77 等),在传递数组时可能已编码如下:

Real*8 A(1000)

Call Sub(A)

Subroutine Sub(A)

Real*8 A(1) ! this was workable since Fortran only passes the pointer/by ref to the first element of A(1000)
                ! modern Fortran may well throw a bounds check warning

在现代 Fortran 中,“等效”是将 A 声明为 Real(DP) A(:) 在 s/r 中(尽管严格来说,有多种设置可以受益于传递数组的边界并使用边界显式声明,但是改天再讲一个冗长的题外话)。

也就是说,Fortran 不按值传递,也不为 Args/Dummy var“制作副本”。调用 s/r 中的 A() 与 s/r 中使用的“相同的 A”(当然,在 s/r 中,可以复制 A() 或其他内容,这会创建额外的工作/空间要求,但那是另一回事)。

主要是由于这个原因,Intent 在很大程度上不会直接影响性能,即使对于大型数组 Arg 等也是如此。

B)关于“按值传递”的混淆:尽管上面的各种回应确实证实使用 Intent 是“不是按值传递”,但它可能有助于澄清问题。

将措辞更改为“意图始终通过引用传递”可能会有所帮助。这与“不按值传递”不同,这是一个重要的微妙之处。值得注意的是,Intent 不仅是“byRef”,而且 Intent 还可以防止按值传递。

尽管有特殊/更复杂的设置(例如混合语言 Fortran DLL 等),其中需要更多额外的讨论,对于“标准 Fortran”的大部分,Args 通过 Ref 传递。这种“意图的微妙性”的演示可以在“The Glazer Guys”示例的简单扩展中看到,如下所示:

subroutine sub(i)
    integer, intent(in) :: i, j
    integer, value     :: iV, jV
    call sub2(i)
    call sub3(i, j, jV, iV)
end
subroutine sub2(i)
    implicit none
    integer i
    i = 7  ! This works since the "intent" information was lost.
end
subroutine sub3(i, j, jV, iV)
    implicit none
    integer, value, Intent(In)          :: i    ! This will work, since passed in byRef, but used locally as byVal
    integer, value, Intent(InOut)       :: j    ! This will FAIL, since ByVal/ByRef collision with calling s/r,
                                                ! ||Error: VALUE attribute conflicts with INTENT(INOUT) attribute at (1)|
    integer, value, Intent(InOut)       :: iV   ! This will FAIL, since ByVal/ByRef collision with calling s/r,
                                                ! ... in spite of "byVal" in calling s/r
                                                ! ||Error: VALUE attribute conflicts with INTENT(INOUT) attribute at (1)|
    integer, value, Intent(Out)         :: jV   ! This will FAIL, since ByVal/ByRef collision with calling s/r
                                                ! ... in spite of "byVal" in calling s/r
                                                ! ||Error: VALUE attribute conflicts with INTENT(OUT) attribute at (1)|
    jV = -7
    iV = 7
end

也就是说,任何具有“Out”方面的内容都必须是“byRef”(至少在正常设置中),因为调用 s/r 期望“byRef”。因此,即使所有 s/r 都将 Args 声明为“Value”,它们也仅在本地为“byVal”(同样在标准设置中)。因此,被调用的 s/r 尝试返回一个被声明为具有任何类型的 Out Intent 的 Value 的 Arg 的任何尝试都将因传递样式的“冲突”而失败。

如果必须是“Out”或“InOut”和“Value”,则不能使用Intent:这不仅仅是简单地说“它不是按值传递”。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top