18

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

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

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

4

3 回答 3

21

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

这意味着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  
于 2010-01-07T23:59:13.007 回答
6
  • intent(in)- 看起来像按值传递(并且对此的更改不会反映在外部代码中),但实际上是按引用传递并且编译器禁止更改它。但它仍然可以改变。
  • intent(out)- 以某种方式通过引用传递,实际上是一个返回参数
  • intent(inout)- 通过引用传递,正常输入/输出参数。

使用intent(out)if is 是显而易见的,以记录您的设计。如果有的话,不要关心非常小的性能增益。(评论表明没有,因为intent(in)在技术上也是通过引用传递的。)

于 2009-06-18T09:07:32.497 回答
1

目前尚不清楚是否确实回答了 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 构造,可以大大提高性能。这些设置通常要求所有 Args 都明确声明其 Intent。

粗略地说,如果编译器事先知道所有 var 的 Intent,那么它可以更轻松有效地优化和“愚蠢检查”代码。

至关重要的是,如果一个人使用 Pure etc 构造,那么很有可能还会有一种“全局安全/卫生”,因为 Pure/Elemental s/p 只能调用其他 Pure/Elemental s/p 等等无法达到“格雷泽盖伊”示例中所示的那种情况。

例如,如果 Sub1() 声明为 Pure,那么 Sub2() 也必须声明为 Pure,然后需要声明所有级别的 Intent,因此“The Glazer Guy”中产生的“garbage out” “示例不可能发生。也就是说,代码将是:

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

...在编译时,这会产生类似

“ ||错误:在 (1)| 的变量定义上下文(赋值)中带有 INTENT(IN) 的虚拟参数 'i'|”

当然,sub2 不必是 Pure 才能将 i 声明为 Intent(In),这将再次提供人们正在寻找的“安全/卫生”。

请注意,即使 i 被声明为 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)|”

因此,对纯/元素构造的严格或广泛依赖将确保(大部分)“全球安全/卫生”。

不可能在所有情况下都使用 Pure/Elemental 等(例如,许多混合语言设置,或者依赖于您无法控制的外部库等)。

尽管如此,始终如一地使用 Intents,并尽可能使用 Pure 等,将产生很多好处,并消除很多悲伤。

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

...这也凸显了同时存在 Intent(InOut) 和 Intent(Out) 的另一个原因,因为 Pure 必须声明所有 Arg 的 Intent,因此会有一些 Arg 仅是 Out,而另一些是 InOut (即,如果没有 In、InOut 和 Out Intent 中的每一个,就很难拥有 Pure's)。

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 中,“等效”是在 s/r 中将 A 声明为 Real(DP) A(:) (尽管严格来说,有各种设置受益于传递数组的边界并明确声明边界,但是将是另一天冗长的题外话)。

也就是说,Fortran 不会按值传递,也不会为 Args/Dummy 变量“制作副本”。调用 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 的值的 Arg 的任何尝试都将由于传递样式的“冲突”而失败。

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

于 2016-12-31T08:49:28.067 回答