不清楚是否有回答到OP问题的部分。此外,回答/讨论中似乎存在许多混淆和各种错误,可能需要一些澄清。
A)OP的问题Re
“然后我也想知道为什么会想要使用intent(out),因为intent(inout)需要更少的工作(不需要复制数据)。”
可能没有得到回答,或者至少不是太直接/正确。
首先,明确一点,Intent属性至少有两个目的:“安全/卫生”和“间接性能”问题(而不是“直接性能”问题)。
1)安全/卫生:帮助生成“安全/合理”的代码,减少“搞砸事情”的机会。因此,Intent(In)不能被覆盖(至少在本地,甚至在某些情况下“全局”下面,请参见下文)。
同样,Intent(Out)要求将Arg分配一个“明确的答案”,从而有助于减少“垃圾”结果。
例如,在计算数学中最常见的问题之一——所谓的“Ax=b问题”的解决方案中,人们正在寻找的“直接结果/答案”是向量x的值。这些值应该是Intent(Out),以确保x被分配一个“明确”的答案。如果将x声明为Intent(InOut)或“没有意图”,那么Fortran会给x分配一些“默认值”(在Debug模式下可能是“零”,但在Release模式下可能是“垃圾”,即Args指针位置的内存中的任何内容),如果用户没有明确地将正确的值分配给x,则会返回“垃圾”。Intent(Out)会“提醒/强制”用户将值明确地分配给x,从而消除这种“(意外的)垃圾”。
在解决过程中,几乎肯定会产生矩阵A的逆。用户可能希望将其逆返回到调用的s/r中,以替换A,这种情况下A应该是Intent(InOut)。
另外,用户可能希望确保不对矩阵A或向量b进行任何更改,这种情况下它们将被声明为Intent(In),从而确保关键值不会被覆盖。
2 a) "间接性能"(以及“全局安全/卫生”):虽然意图并非直接影响性能,但它们会间接地影响。特别是某些类型的优化,尤其是Fortran纯净和元素结构,可以产生大大改善的性能。这些设置通常要求所有Args明确声明其Intent。
粗略地说,如果编译器事先知道所有变量的Intent,则可以更轻松有效地进行代码优化和“愚蠢检查”。
至关重要的是,如果使用Pure等结构,则很有可能也会出现“一种全局安全/卫生”,因为纯/元素s/p只能调用其他纯/元素s/p,因此不可能出现“The Glazer Guy”的示例中所示的情况。
例如,如果将Sub1()声明为Pure,则还必须将Sub2()声明为Pure,然后需要在所有级别上声明Intents,因此在“The Glazer Guy”的示例中产生的“垃圾输出”是不可能发生的。也就是说,代码将是:
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(in) :: i
i = 7
end subroutine sub2_P
当编译时,它会产生类似于
" ||错误:在变量定义上下文(赋值)中具有INTENT(IN)的虚拟参数'i' |(1)| "
当然,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
end subroutine sub2_P
在编译时,这将产生类似于
"||错误:变量定义上下文中的INTENT(IN)虚拟参数 'i'(实参为INTENT = OUT / INOUT)(位于1处)|"
因此,严格或广泛依赖Pure / Elemental结构将确保(大多数情况下)“全局安全/卫生”。
在所有情况下都不可能使用Pure / Elemental等(例如,许多混合语言设置,或者依赖于超出您控制范围的外部库等)。
尽管如此,在可能的情况下始终一致地使用Intents,以及Pure等,将产生很多好处,并消除很多烦恼。
当有可能时,人们可以简单地养成随时声明Intents的习惯,无论是否为Pure ... 这是推荐的编码实践。
...这也带来了存在BOTH Intent(InOut)和Intent(Out)的另一个原因,因为Pure必须声明所有Arg的Intent,所以会有一些仅为Out的Arg,而其他Arg则为InOut(即,很难没有每个In, InOut和Out Intents的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(:)是在子程序中的"等效方式"(尽管严格来说,有各种设置可以从传递数组的边界和显式声明边界中受益,但那将是另一天的冗长讨论)。
也就是说,Fortran不是按值传递,也不会为Args / Dummy变量“制作副本”。调用s/r中的A()与s/r中使用的“A”相同(当然,在s/r中,可以复制A()或其他内容,这将创建额外的工作/空间要求,但这是另一回事)。
主要是由于这个原因,Intent并不直接对性能产生很大影响,即使是大型数组Arg等。
关于“按值传递”的混淆问题:虽然上面的各种响应确实确认使用Intent不是“按值传递”,但澄清此事可能会有所帮助。
更改措辞为“Intent始终按引用传递”可能有所帮助。这并不意味着“不按值传递”,这是一个重要的微妙之处。特别是,Intents不仅是“按引用传递”,而且还可以防止按值传递。
虽然有一些特殊/更复杂的设置(例如混合语言Fortran DLL等),需要进行更多的讨论,但对于大部分的“标准Fortran”,参数是通过引用传递的。可以通过简单扩展“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
end
subroutine sub3(i, j, jV, iV)
implicit none
integer, value, Intent(In) :: i
integer, value, Intent(InOut) :: j
integer, value, Intent(InOut) :: iV
integer, value, Intent(Out) :: jV
jV = -7
iV = 7
end
换句话说,任何具有“Out”方面的内容都必须是“byRef”(至少在正常设置中),因为调用s/r期望是“byRef”。因此,即使所有的s/r将Args声明为“Value”,它们也仅在本地是“byVal”(同样是在标准设置中)。因此,被调用的s/r试图返回一个声明为Value且带有任何Out意图的Arg,将由于传递风格的“冲突”而失败。
如果必须是“Out”或“InOut”和“Value”,则不能使用Intent:这比简单地说“它不是按值传递”更为复杂。