Fortran 传递带括号的参数可防止更改
Fortran passing parameters with brackets prevents changes
在这个 question 中,我询问了一种显式防止传递的参数发生变化的方法。一个明显的解决方案是定义参数的副本并在这些副本上运行算法。但是在评论中我指出了一个事实,即我可以调用该函数并将我不想更改的参数括在方括号中。这与创建传递的变量的副本具有相同的效果,这样它就不会改变。但我不明白它是如何工作的以及括号实际上在做什么。那么有人可以向我解释一下吗?
这是一个简单的示例,其中的行为如我所描述的那样发生。
1 program argTest
2 implicit none
3 real :: a, b, c
4
5 interface !optional interface
6 subroutine change(a,b,c)
7 real :: a, b, c
8 end subroutine change
9 end interface
10
11 write(*,*) 'Input a,b,c: '
12 read(*,*) a, b, c
13
14 write(*,*) 'Values at start:'
15 write(*,*)'a:', a
16 write(*,*)'b:', b
17 write(*,*)'c:', c
18
19
20 call change((a),b,c)
21 write(*,*)'Values after calling change with brackets around a:'
22 write(*,*)'a:', a
23 write(*,*)'b:', b
24 write(*,*)'c:', c
25
26
27 call change(a,b,c)
28 write(*,*)'Values after calling change without brackets:'
29 write(*,*)'a:', a
30 write(*,*)'b:', b
31 write(*,*)'c:', c
32
33 end program argTest
34
35
36 subroutine change(a,b,c)
37 real :: a, b, c
38
39 a = a*2
40 b = b*3
41 c = c*4
42
43 end subroutine change
44
45
46
我认为解释是这样的,虽然我不能指出标准中明确的部分,...
(a)
是一个表达式,其结果与 a
相同。传递给子例程的是计算该表达式的结果。 Fortran 不允许对该结果进行赋值,就像将 cos(a)
传递给子例程一样。我猜 (a)
的结果几乎与 a
的副本完全相同,这可能解释了令人困惑的 OP 行为。
我在这台计算机上没有 Fortran,但如果我有,我会尝试更多 a
和 (a)
之间的差异可能很重要的情况,例如
(a) = some_value
看看编译器对它们做了什么。
下面@IanH 的评论指出了语言标准的相关部分。
使用(非标准)loc() 函数实际打印实际参数和虚拟参数的地址并比较它们可能很有趣,例如:
program main
implicit none
integer :: a
a = 5
print *, "address(a) = ", loc( a )
call sub( 100 * a )
call sub( 1 * a )
call sub( 1 * (a) )
call sub( (a) )
call sub( a )
contains
subroutine sub( n )
integer :: n
n = n + 1
print "(2(a,i4,3x),a,i18)", "a=", a, " n=", n, "address(n) =", loc( n )
end subroutine
end program
输出变成这样,说明实际上传递了一个包含表达式结果的临时变量给sub()
(最后一种情况除外)
# gfortran-6
address(a) = 140734780422480
a= 5 n= 501 address(n) = 140734780422468
a= 5 n= 6 address(n) = 140734780422464
a= 5 n= 6 address(n) = 140734780422460
a= 5 n= 6 address(n) = 140734780422456
a= 6 n= 6 address(n) = 140734780422480
# ifort-16
address(a) = 140734590990224
a= 5 n= 501 address(n) = 140734590990208
a= 5 n= 6 address(n) = 140734590990212
a= 5 n= 6 address(n) = 140734590990216
a= 5 n= 6 address(n) = 140734590990220
a= 6 n= 6 address(n) = 140734590990224
# Oracle fortran 12.5
address(a) = 6296328
a= 5 n= 501 address(n) = 140737477281416
a= 5 n= 6 address(n) = 140737477281420
a= 5 n= 6 address(n) = 140737477281424
a= 5 n= 6 address(n) = 140737477281428
a= 6 n= 6 address(n) = 6296328
(有趣的是,Oracle 出于某种原因为 a
使用了一个非常小的地址......尽管其他编译器使用非常相似的地址。)
[ 编辑 ] 根据 Ian 的上述回答,将值分配给表达式产生的内存是非法的(这是一个值 = 常量,而不是变量)。所以请把上面的代码当作是尝试确认用 (...) 传递的内容与原来的不同 a
.
在问题代码的上下文中,语法 (a)
是一个表达式。在没有指针结果的情况下,计算表达式以产生一个值。在这种情况下,表达式的值与变量 a
.
的值相同
虽然表达式 (a)
和变量 a
的计算结果具有相同的值,但它们不是一回事 - 变量的值与变量本身。这用于某些情况,其中需要将同一变量同时作为输入参数和单独的输出参数提供,否则 运行 会违反 Fortran 对参数别名的限制。
HOWEVER - 如上所述 - 在没有指针结果的情况下,计算表达式的结果是一个值,而不是一个变量。 您不能重新定义值。从概念上讲,"I am going to change the meaning of the value 2
" 或 "I am going to change the meaning of the result of evaluating 1 + 1
" 是没有意义的。
当您将此类表达式用作实际参数时,它不得与在过程中重新定义的伪参数相关联。
在子例程 change
中,与表达式 (a)
的值关联的伪参数被重新定义。这是不合格的。
是否制作副本是您不能(也不能)指望的实现细节 - 链接问题中的评论不准确。例如,意识到上述限制的编译器知道子例程 change
实际上不能以符合要求的方式更改第一个参数,可能知道 a
对 change
不可见,因此决定不需要为表达式结果制作 a
的临时副本。
如果您需要临时复制某些内容,请编写制作副本的语句。
real :: tmp_a
...
tmp_a = a
call change(tmp_a, b, c)
在这个 question 中,我询问了一种显式防止传递的参数发生变化的方法。一个明显的解决方案是定义参数的副本并在这些副本上运行算法。但是在评论中我指出了一个事实,即我可以调用该函数并将我不想更改的参数括在方括号中。这与创建传递的变量的副本具有相同的效果,这样它就不会改变。但我不明白它是如何工作的以及括号实际上在做什么。那么有人可以向我解释一下吗?
这是一个简单的示例,其中的行为如我所描述的那样发生。
1 program argTest
2 implicit none
3 real :: a, b, c
4
5 interface !optional interface
6 subroutine change(a,b,c)
7 real :: a, b, c
8 end subroutine change
9 end interface
10
11 write(*,*) 'Input a,b,c: '
12 read(*,*) a, b, c
13
14 write(*,*) 'Values at start:'
15 write(*,*)'a:', a
16 write(*,*)'b:', b
17 write(*,*)'c:', c
18
19
20 call change((a),b,c)
21 write(*,*)'Values after calling change with brackets around a:'
22 write(*,*)'a:', a
23 write(*,*)'b:', b
24 write(*,*)'c:', c
25
26
27 call change(a,b,c)
28 write(*,*)'Values after calling change without brackets:'
29 write(*,*)'a:', a
30 write(*,*)'b:', b
31 write(*,*)'c:', c
32
33 end program argTest
34
35
36 subroutine change(a,b,c)
37 real :: a, b, c
38
39 a = a*2
40 b = b*3
41 c = c*4
42
43 end subroutine change
44
45
46
我认为解释是这样的,虽然我不能指出标准中明确的部分,...
(a)
是一个表达式,其结果与 a
相同。传递给子例程的是计算该表达式的结果。 Fortran 不允许对该结果进行赋值,就像将 cos(a)
传递给子例程一样。我猜 (a)
的结果几乎与 a
的副本完全相同,这可能解释了令人困惑的 OP 行为。
我在这台计算机上没有 Fortran,但如果我有,我会尝试更多 a
和 (a)
之间的差异可能很重要的情况,例如
(a) = some_value
看看编译器对它们做了什么。
下面@IanH 的评论指出了语言标准的相关部分。
使用(非标准)loc() 函数实际打印实际参数和虚拟参数的地址并比较它们可能很有趣,例如:
program main
implicit none
integer :: a
a = 5
print *, "address(a) = ", loc( a )
call sub( 100 * a )
call sub( 1 * a )
call sub( 1 * (a) )
call sub( (a) )
call sub( a )
contains
subroutine sub( n )
integer :: n
n = n + 1
print "(2(a,i4,3x),a,i18)", "a=", a, " n=", n, "address(n) =", loc( n )
end subroutine
end program
输出变成这样,说明实际上传递了一个包含表达式结果的临时变量给sub()
(最后一种情况除外)
# gfortran-6
address(a) = 140734780422480
a= 5 n= 501 address(n) = 140734780422468
a= 5 n= 6 address(n) = 140734780422464
a= 5 n= 6 address(n) = 140734780422460
a= 5 n= 6 address(n) = 140734780422456
a= 6 n= 6 address(n) = 140734780422480
# ifort-16
address(a) = 140734590990224
a= 5 n= 501 address(n) = 140734590990208
a= 5 n= 6 address(n) = 140734590990212
a= 5 n= 6 address(n) = 140734590990216
a= 5 n= 6 address(n) = 140734590990220
a= 6 n= 6 address(n) = 140734590990224
# Oracle fortran 12.5
address(a) = 6296328
a= 5 n= 501 address(n) = 140737477281416
a= 5 n= 6 address(n) = 140737477281420
a= 5 n= 6 address(n) = 140737477281424
a= 5 n= 6 address(n) = 140737477281428
a= 6 n= 6 address(n) = 6296328
(有趣的是,Oracle 出于某种原因为 a
使用了一个非常小的地址......尽管其他编译器使用非常相似的地址。)
[ 编辑 ] 根据 Ian 的上述回答,将值分配给表达式产生的内存是非法的(这是一个值 = 常量,而不是变量)。所以请把上面的代码当作是尝试确认用 (...) 传递的内容与原来的不同 a
.
在问题代码的上下文中,语法 (a)
是一个表达式。在没有指针结果的情况下,计算表达式以产生一个值。在这种情况下,表达式的值与变量 a
.
虽然表达式 (a)
和变量 a
的计算结果具有相同的值,但它们不是一回事 - 变量的值与变量本身。这用于某些情况,其中需要将同一变量同时作为输入参数和单独的输出参数提供,否则 运行 会违反 Fortran 对参数别名的限制。
HOWEVER - 如上所述 - 在没有指针结果的情况下,计算表达式的结果是一个值,而不是一个变量。 您不能重新定义值。从概念上讲,"I am going to change the meaning of the value 2
" 或 "I am going to change the meaning of the result of evaluating 1 + 1
" 是没有意义的。
当您将此类表达式用作实际参数时,它不得与在过程中重新定义的伪参数相关联。
在子例程 change
中,与表达式 (a)
的值关联的伪参数被重新定义。这是不合格的。
是否制作副本是您不能(也不能)指望的实现细节 - 链接问题中的评论不准确。例如,意识到上述限制的编译器知道子例程 change
实际上不能以符合要求的方式更改第一个参数,可能知道 a
对 change
不可见,因此决定不需要为表达式结果制作 a
的临时副本。
如果您需要临时复制某些内容,请编写制作副本的语句。
real :: tmp_a
...
tmp_a = a
call change(tmp_a, b, c)